為什麼操作DOM會影響WEB應用的性能?
- 2019 年 10 月 3 日
- 筆記
面試官經常會問你:「平時工作中,你怎麼優化自己應用的性能?」
你回答如下:「我平時遵循以下幾條原則來優化我的項目、以提高性能,主要有:」
a. 減少DOM操作的次數(減少DOM的獲取與修改次數)
b. 減少網路請求
c. 壓縮、合併靜態資源文件(css、js、img等)
d. 小圖片文件base64化處理
e. js少用全局變數
f. …
Bingo!此時,你給自己刨了個可以把自己埋住的大坑。
因為面試官可能會追問你:「為什麼減少DOM操作可以提高性能?」
為什麼呢?
_______
1、dom是什麼?ES和 DOM是什麼關係?
DOM
就是Document Object Model
,文檔對象模型,裡邊是介面,即方法函數。我們通過調用並傳指定參數來使用。
官方定義:DOM是一個獨立於語言的、用於操作XML和HTML文檔的程式介面(API)。在瀏覽器中主要用於與HTML文檔打交道,並且使用DOM API用來訪問文檔中的數據。
DOM是個與ES語言無關的API,它在瀏覽器中的介面卻是用JavaScript來實現的,DOM就成了現在JS編碼中的重要部分。
1-1、各大瀏覽器中,DOM的位置和JavaScript的位置(渲染引擎與JS引擎相互獨立)
瀏覽器 | JS位置 | DOM位置 |
---|---|---|
IE | JavaScript的實現名為JScript,位於jscript.dll文件中 | DOM的實現則存在另一個庫中,名為mshtml.dll(內部稱為trident) |
safari | JavaScript部分是由獨立的SquirelFish引擎來實現。 | DOM和渲染是使用webkit中的webcore實現 |
google chrome | JavaScript引擎是他們自己研發的,名為V8。 | 使用webkit中的webCore庫來渲染頁面 |
firefox | JavaScript引擎名為TraceMonkey | 渲染引擎Gecko |
1-2、ES和 DOM是兩種東西
ES通過DOM介面來獲取文檔中的元素。
正因為瀏覽器中通常把DOM和ECMAScript獨立實現。使得二者相互獨立,就像兩座孤島。
所以ES每次操作DOM時,ES和DOM之間就像兩個橋之間需要過車輛。
每次鏈接就都需要搭建一個橋樑,搭橋還是小事,ES請求DOM的車輛過橋時,會經過一個收費站,每次都會被收費。JS引擎會消耗瀏覽器的性能進行繳費。
而車輛通過後橋就銷毀,下次鏈接重新搭橋二次繳費。所以說JS與DOM每次連接都需要消耗性能 。
也正因此,有了每操作一次DOM就多做點事的理念,儘可能以最少的次數處理最多的DOM操作,以實現每過一次橋多拉點貨的效果。
(VUE也正是這種理念,操作虛擬dom減少性能消耗,因此vue性能更優,另個話題來說。)
2、ES每次訪問DOM都需要消耗性能:
正因為二者相互獨立,所以每次鏈接、每次訪問DOM都會消耗性能!! 可以說操作dom是十分昂貴的!!寧可處理一萬次js,也不操作一次dom!!
3、ES每次修改DOM元素的代價則更為昂貴
像上邊說的,每次操作DOM之前,就會先訪問DOM,所以也會消耗性能。
在此基礎上,因為修改DOM會導致瀏覽器重新計算頁面的幾何變化、引發瀏覽器模板引擎的重排(迴流 – 回滾流程)和重繪,進而更加消耗性能。
4、瀏覽器渲染引擎的工作原理、工作流程是什麼?
瀏覽器下載完頁面中的所有資源(比如HTML、JavaScript、CSS、圖片等)後,會發生如下的6步過程:
- 解析HTML,構建DOM樹(DOM Tree)
- 解析CSS,生成CSS規則樹(CSSOM Tree)
- 合併DOM樹和CSS規則樹,生成渲染樹render樹(render Tree)
- 布局render樹,根據生成的render樹來對各元素尺寸、位置進行計算,得到每個節點的幾何資訊。(根據視口的大小來計算元素的位置和大小)(重排會走這一步)
- 繪製render樹,繪製頁面像素資訊(根據render樹上每個節點的幾何資訊,得到每個節點的像素數)(重繪會走這一步)
- 瀏覽器會將各層節點的像素資訊發送給GPU,GPU將各層合成、繪製展示到頁面上
4-1、瀏覽器渲染引擎是如何生成渲染樹(render Tree)的?
先看一張圖:
由上圖得知如下流程:
- 從DOM Tree的根節點開始遍歷每一個可見節點(除meta、link、script等這些標籤;除display:none;的元素)
- 對於每個可見節點,在CSSOM中找到對應規則並將樣式規則應用到對應節點上。
- 根據每一個可見節點,以及其對應的樣式,組合生成渲染樹。
不可見節點: 不會渲染輸出的節點(不會顯示在螢幕上的節點)有以下幾種
- meta、link、script等標籤;
- 通過css進行隱藏的節點,即display:none;(opacity對人類不可見,電腦還能看見,所以還會渲染。)(那visibility為隱藏的元素會不會被渲染呢?做個試驗,一個div設置visibility不可見,左浮動,周圍全是文字,看文字環繞是否讓出一塊空白區域。最後試驗證明確實繞出了一段空白的位置,說明visibility和opacity設置的不可見只是對人類肉眼不可見,電腦還是會在生成render Tree的時候計算位置資訊並把他繪製出來。試驗結果如下圖:)
5、什麼是瀏覽器渲染引擎的重排和重繪?
5-1、重排
當DOM的變化影響了元素的幾何屬性(寬和高),瀏覽器需要重新計算元素的幾何屬性,同樣其他相鄰元素的幾何屬性和位置也會因此受到影響。瀏覽器會使渲染樹中受到影響的部分失效,並重新構造渲染樹。這個過程稱為「重排」。
換句話說,改變了頁面中某元素的位置、尺寸大小,進而也就改變了他的佔地面積。那這個元素修改了佔地面積後,其後緊鄰的元素就得挪動位置。給她讓地兒(或者向前趕趕)。緊鄰的元素挪動了,那緊鄰元素後邊的元素也會連鎖效應式的修改。這就好比一排人排隊。前邊的人突然變胖了、變瘦了、向前挪了、向後擠了、都會導致隊伍中後邊的人也跟隨之改變位置,由此導致一連串的人都挪動位置。這時瀏覽器就要重新排版各個受到影響的元素的位置。反應在渲染引擎的工作流程中也就是瀏覽器需要重新計算元素位置資訊並布局render樹。這就是重排。
5-2、重繪
完成重排後,瀏覽器會重新繪製受影響的部分到螢幕中,該過程稱為重繪。
因為重排在重繪的上一步,所以重排發生後自然會導致重繪。這個很好理解。
6、什麼時候會引發重排?
當頁面布局和幾何屬性改變時就需要重排:
(核心就是:只要某個屬性能導致位置資訊發生改變,就會觸發重排 )
- 添加或刪除可見的DOM元素。(一堆人排隊,添加即中間插入了一個人/刪除即中間一個人走了,勢必會影響後邊排隊的人的位置資訊也發生改變)
- 元素位置改變(重排就是因為位置資訊改變了)
- 元素尺寸改變( 外邊距、內邊距、邊框厚度、寬度、高度等)
- 內容改變,例:文本數量/內容改變、或圖片被另一個不同尺寸的圖片替代、字體大小改變、(文字加粗?)導致DOM元素位置、面積改變。【計算會消耗CPU的能力】
- 頁面渲染器初始化(這算重走流程吧,肯定要重排)
- 瀏覽器窗口尺寸改變(位置資訊會被迫調整,發生重排。見下圖的gif圖,一個頁面中div元素的位置不受視口調整而修改,也會引發重排)【消耗GPU的計算能力】
試驗:resize視口,一個頁面中div元素的位置不受視口調整而修改,也會引發重排
7、打斷瀏覽器的優化步驟
現代瀏覽器是相當完善的了,因為多次操作DOM會觸發重排重繪、消耗性能。所以除了我們人為的、有意識的去控制操作DOM次數以外,瀏覽器在設計上進行了優化,也會智慧的「節流」操作DOM,比如實現隊列化修改、批量執行。
解釋來說就是,瀏覽器會有一個「隊列」,用以存放(攢著)需要操作DOM的js程式。每當執行一次js操作dom的程式碼,這個隊列里就先暫存一個程式。等到一段時間後,瀏覽器再集中、批量的鏈接一次"ES島"和"DOM島"(就是讓JS引擎去鏈接渲染引擎),進而觸發一次DOM操作。你可以形象的理解為「過一段時間發一班車」。
但是我們人類感知不到啊,可能會因為誤操作打斷瀏覽器的「節流」步驟。迫使瀏覽器中斷當前的「等待」,去趕緊、立馬進行一次dom操作。讓瀏覽器趕緊執行完他攢在「隊列」里的JS操作DOM的程式後返回最新的DOM位置資訊給我們。這就好像電梯門定時自動關閉,但是你卻手動按了關門按鈕強迫關門一樣。
這種情況就發生在我們獲取DOM資訊的時候:
打斷瀏覽器優化,強迫觸發重排的屬性:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()
因為要跟瀏覽器請求最新的DOM資訊,所以瀏覽器就得趕緊讓JS引擎去渲染引擎那裡進行一次DOM操作。
8、什麼時候會引發重繪?
- 重排必然引發重繪,這是肯定的。因為瀏覽器的工作流程就是排版後渲染。重排會迴流(回滾流程)到排版階段,排版後需要重新繪製頁面。
- 單獨觸發重繪的情況:
除元素尺寸、位置發生改變以外的情況,(比如字體顏色、背景色等發生改變)。(我懷疑文字加粗也會觸發重排,但是我沒有證據。理論上來說如果在一個固定尺寸的div內加粗文字,應該不會影響後邊元素的重排,但可能該div內部的其他相鄰文字或元素會發生重排。)
試驗gif圖:
(想到一個驗證只發生重繪的情況,那就是後邊也加點元素,如果重排了,後邊的元素在控制台的檢測下也會閃綠光。)
9、為什麼不提倡重排和重繪?
既然知道了這個dom操作會觸發重排、重繪。那又是為什麼要盡量避免重排和重繪呢?
換句話說,重排和重繪的副作用是什麼?缺點是什麼?
這就要引入CPU和GPU了。
重排會佔用CPU,dom元素位置計算會消耗CPU的算力,所以應該盡量減少CPU的佔用,使電腦不卡頓。
重繪會佔用GPU,渲染頁面時會消耗GPU的算力。
GPU的分類:
- 家用GPU
適合做貼圖、特效、光影等效果。不適合畫圖形。 - 專業GPU
適合畫圖形。不適合做貼圖、特效、光影等效果。
DOM操作基本就是畫圖形的,但瀏覽器中用的就是家用GPU,其畫圖形耗費的性能是專業GPU的幾十倍。所以不提倡頻繁用裝有家用GPU的瀏覽器繪製頁面。也就是不提倡頻繁觸發重繪。
10、總結: 為什麼操作DOM非常昂貴?
- ES和 DOM是兩種東西,每次連接都需要消耗性能
- 操作DOM會導致重排和重繪,重排會佔用、消耗CPU; 重繪會佔用、消耗GPU
11、控制台觀察一個頁面的重排和重繪現象
因為重排必然會引發重繪,所以在瀏覽器的開發者工具中提供了一個檢測重繪的按鈕。尋找和打開步驟如下圖:
各css屬性對重排重繪的影響:https://csstriggers.com/
可以關注我的微信公眾號看更多總結文章~