<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                從本節開始,我們要關心的兩大核心問題就是:“DOM 為什么這么慢”以及“如何使 DOM 變快”。 后者是一個比“生存還是毀滅”更加經典的問題。不僅我們為它“肝腸寸斷”,許多優秀前端框架的作者大大們也曾為其絞盡腦汁。這一點可喜可賀——研究的人越多,產出優秀實踐的概率就越大。因此在本章的方法論環節,我們不僅會根據 DOM 特性及渲染原理為大家講解基本的優化思路,還會涉及到一部分生產實踐。 循著這個思路,我們把 DOM 優化這塊劃分為三個小專題:“DOM 優化思路”、“異步更新策略”及“回流與重繪”。本節對應第一個小專題。三個小專題休戚與共、你儂我儂,在思路上相互依賴、一脈相承,因此此處**嚴格禁止任何姿勢的跳讀行為**。 考慮到本節內容與上一節有著密不可分的關系,因此**強烈不建議沒有讀完上一節的同學直接跳讀本節**。 ## 望聞問切:DOM 為什么這么慢 ### 因為收了“過路費” > 把 DOM 和 JavaScript 各自想象成一個島嶼,它們之間用收費橋梁連接。——《高性能 JavaScript》 JS 是很快的,在 JS 中修改 DOM 對象也是很快的。在JS的世界里,一切是簡單的、迅速的。但 DOM 操作并非 JS 一個人的獨舞,而是兩個模塊之間的協作。 上一節我們提到,JS 引擎和渲染引擎(瀏覽器內核)是獨立實現的。當我們用 JS 去操作 DOM 時,本質上是 JS 引擎和渲染引擎之間進行了“跨界交流”。這個“跨界交流”的實現并不簡單,它依賴了橋接接口作為“橋梁”(如下圖)。 ![](https://user-gold-cdn.xitu.io/2018/9/29/166254bce949ca58?w=618&h=242&f=png&s=51738) 過“橋”要收費——這個開銷本身就是不可忽略的。我們每操作一次 DOM(不管是為了修改還是僅僅為了訪問其值),都要過一次“橋”。過“橋”的次數一多,就會產生比較明顯的性能問題。因此“減少 DOM 操作”的建議,并非空穴來風。 ### 對 DOM 的修改引發樣式的更迭 過橋很慢,到了橋對岸,我們的更改操作帶來的結果也很慢。 很多時候,我們對 DOM 的操作都不會局限于訪問,而是為了修改它。當我們對 DOM 的修改會引發它外觀(樣式)上的改變時,就會觸發**回流**或**重繪**。 這個過程本質上還是因為我們對 DOM 的修改觸發了渲染樹(Render Tree)的變化所導致的: ![](https://user-gold-cdn.xitu.io/2018/9/29/1662558836a66620?w=644&h=321&f=png&s=27095) * 回流:當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然后再將計算的結果繪制出來。這個過程就是回流(也叫重排)。 * 重繪:當我們對 DOM 的修改導致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式(跳過了上圖所示的回流環節)。這個過程叫做重繪。 由此我們可以看出,**重繪不一定導致回流,回流一定會導致重繪**。硬要比較的話,回流比重繪做的事情更多,帶來的開銷也更大。但這兩個說到底都是吃性能的,所以都不是什么善茬。我們在開發中,要從代碼層面出發,盡可能把回流和重繪的次數最小化。 ## 藥到病除:給你的 DOM “提提速” 知道了 DOM 慢的原因,我們就可以對癥下藥了。 ### 減少 DOM 操作:少交“過路費”、避免過度渲染 我們來看這樣一個??,HTML 內容如下: ``` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>DOM操作測試</title> </head> <body> <div id="container"></div> </body> </html> ``` 此時我有一個假需求——我想往 container 元素里寫 10000 句一樣的話。如果我這么做: ``` for(var count=0;count<10000;count++){ document.getElementById('container').innerHTML+='<span>我是一個小測試</span>' } ``` 這段代碼有兩個明顯的可優化點。 第一點,**過路費交太多了**。我們每一次循環都調用 DOM 接口重新獲取了一次 container 元素,相當于每次循環都交了一次過路費。前后交了 10000 次過路費,但其中 9999 次過路費都可以用**緩存變量**的方式節省下來: ``` // 只獲取一次container let container = document.getElementById('container') for(let count=0;count<10000;count++){ container.innerHTML += '<span>我是一個小測試</span>' } ``` 第二點,**不必要的 DOM 更改太多了**。我們的 10000 次循環里,修改了 10000 次 DOM 樹。我們前面說過,對 DOM 的修改會引發渲染樹的改變、進而去走一個(可能的)回流或重繪的過程,而這個過程的開銷是很“貴”的。這么貴的操作,我們竟然重復執行了 N 多次!其實我們可以通過**就事論事**的方式節省下來不必要的渲染: ``` let container = document.getElementById('container') let content = '' for(let count=0;count<10000;count++){ // 先對內容進行操作 content += '<span>我是一個小測試</span>' } // 內容處理好了,最后再觸發DOM的更改 container.innerHTML = content ``` 所謂“就事論事”,就像大家所看到的:JS 層面的事情,JS 自己去處理,處理好了,再來找 DOM 打報告。 事實上,考慮JS 的運行速度,比 DOM 快得多這個特性。我們減少 DOM 操作的核心思路,就是**讓 JS 去給 DOM 分壓**。 這個思路,在 [DOM Fragment](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment) 中體現得淋漓盡致。 > DocumentFragment 接口表示一個沒有父級文件的最小文檔對象。它被當做一個輕量版的 Document 使用,用于存儲已排好版的或尚未打理好格式的XML片段。因為 DocumentFragment 不是真實 DOM 樹的一部分,它的變化不會引起 DOM 樹的重新渲染的操作(reflow),且不會導致性能等問題。 在我們上面的例子里,字符串變量 content 就扮演著一個 DOM Fragment 的角色。其實無論字符串變量也好,DOM Fragment 也罷,它們本質上都作為脫離了真實 DOM 樹的**容器**出現,用于緩存批量化的 DOM 操作。 前面我們直接用 innerHTML 去拼接目標內容,這樣做固然有用,但卻不夠優雅。相比之下,DOM Fragment 可以幫助我們用更加結構化的方式去達成同樣的目的,從而在維持性能的同時,保住我們代碼的可拓展和可維護性。我們現在用 DOM Fragment 來改寫上面的例子: ``` let container = document.getElementById('container') // 創建一個DOM Fragment對象作為容器 let content = document.createDocumentFragment() for(let count=0;count<10000;count++){ // span此時可以通過DOM API去創建 let oSpan = document.createElement("span") oSpan.innerHTML = '我是一個小測試' // 像操作真實DOM一樣操作DOM Fragment對象 content.appendChild(oSpan) } // 內容處理好了,最后再觸發真實DOM的更改 container.appendChild(content) ``` 我們運行這段代碼,可以得到與前面兩種寫法相同的運行結果。 可以看出,DOM Fragment 對象允許我們像操作真實 DOM 一樣去調用各種各樣的 DOM API,我們的代碼質量因此得到了保證。并且它的身份也非常純粹:當我們試圖將其 append 進真實 DOM 時,它會在乖乖交出自身緩存的所有后代節點后**全身而退**,完美地完成一個容器的使命,而不會出現在真實的 DOM 結構中。這種結構化、干凈利落的特性,使得 DOM Fragment 作為經典的性能優化手段大受歡迎,這一點在 jQuery、Vue 等優秀前端框架的源碼中均有體現。 相比 DOM 命題的博大精深,一個簡單的循環 Demo 顯然不能說明所有問題。不過不用著急,在本節,我只希望大家能牢記原理與宏觀思路。“藥到病除”到這里才剛剛開了個頭,下個小節,我們將深挖事件循環機制,從而深入 JS 層面的生產實踐。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看