關於迴流與重繪優化的探索
杭州下雪了,冷到不行,在家躺在床上玩手機,開啟微信進入前端交流群裡日常吹水,看到大佬在群裡發了一篇文章 ofollow,noindex">你應該要知道的重繪與重排 ,文章裡有一段騷操作,就是為了減少重繪與重排,合併樣式操作,這個騷操作成功的引起了我的注意,然後開啟了我的探索。
正文
前言中描述的合併樣式的騷操作是如下:
var el = document.querySelector('div'); el.style.borderLeft = '1px'; el.style.borderRight = '2px'; el.style.padding = '5px'; 複製程式碼
原文描述的大概意思是這段程式碼多次對 DOM 的修改和對樣式的修改,頁面會進行多次迴流或者重繪,應該進行如下優化:
var el = document.querySelector('div'); el.style.cssText = 'border-left: 1px; border-right: 1px; padding: 5px;' 複製程式碼
這樣的優化在以前我剛開始學習前端的時候,經常也在一些相關的效能優化的文章裡看到,因為一直沒有探究過,概念裡一直覺得自己應該把多次 DOM 的樣式的修改合併在一起,這樣效率會更高,直到後來,自己對瀏覽器的程序與執行緒慢慢有了瞭解,曾經也寫過一篇部落格, 淺談瀏覽器多程序與JS執行緒 ,其中有一個概念是, JS執行緒與GUI渲染執行緒是互斥關係
,大概的意思就是當js引擎在執行js程式碼的時候,瀏覽器的渲染引擎是被凍結了的,無法渲染頁面的,必須等待js引擎空閒了才能渲染頁面。
這個概念, JS執行緒與GUI渲染執行緒是互斥關係
與上面描述的騷操作似乎有點衝突,也就是當我們對 el.style
進行一系列賦值的時候,渲染引擎是被凍結的狀態,怎麼會進行多次重繪或者回流?帶著這樣的疑問,寫了一個小demo,程式碼如下。
<!DOCTYPE html> <html> <head> <title>測試頁</title> <style> #box { width: 109px; height: 100px; background-color: lightsteelblue; border-style: solid; } </style> </head> <body> <div id="box"></div> </body> <script> var box = document.getElementById('box'); var toggle = 0; var time = 500; function toggleFun() { var borderWidth = toggle ? 20 : 0; var borderColor = toggle ? 'coral' : 'transparent'; if (toggle) { box.style.borderWidth = '50px'; box.style.borderWidth = borderWidth + 'px'; box.style.borderColor = borderColor; } else { box.style.cssText = 'border: ' + borderWidth + 'px solid' + borderColor; } toggle = toggle ? 0 : 1; } setInterval(toggleFun, time) </script> </html> 複製程式碼
程式碼大概的意思就是定時以兩種操作設定樣式,收集瀏覽器的迴流或者重繪次數。
開啟chrome的開發者工具,切換到 Performance
選項卡,點選左上角的圓 ○,開始 record
,等幾秒後 stop
,點選 Frames
檢視 Event log
選項卡,內容如下:

大概可以看到, Recalculate Style -> Layout -> Update Layer Tree -> Paint -> Composite Layers
這個過程在迴圈進行,觸發的目的碼是第25行程式碼合29行程式碼,也就是 box.style.borderWidth = '50px';
和 box.style.cssText = 'border: ' + borderWidth + 'px solid' + borderColor;
。
首先回顧一下瀏覽器渲染頁面的流程:
- 請求拿到html報文。
- 同時解析生成CSS規則樹和DOM樹。
- 合併CSS規則樹和DOM樹,生成render樹。
- 渲染程序根據render樹進行Layout。
- 繪製paint頁面。
然後在看看上面的過程,可以容易看出,
Recalculate Style Layout Update Layer Tree Paint Composite Layers
由上面過程得到結果,當在同一執行任務裡面對DOM的樣式進行多次操作的時候,只會進行一次迴流或者重繪,也就是說,只要我們的js引擎時候忙碌的,渲染引擎是凍結的時候,無論對DOM樣式進行多少次操作,都只會進行一次迴流或者重繪,也就是說前面說的 合併樣式
優化是無效的。
這個時候,我對上面過程又產生了新的疑問,為什麼要 Paint
之後在 Composite Layers
呢?為什麼不把所有層合併完了在繪製頁面呢?
.........................(看搜尋相關資料去了)
翻看資料結束後,我得到以下理解。
首先理解 layer
概念,可以理解成PS裡面的圖層,我們知道PS檔案最後儲存層PSD檔案,當圖層越多的時候,PSD檔案就越大,在我們的瀏覽器裡面也是一樣的,我們的layer越多,所佔的記憶體就越大。
然後理解 Paint
真正做的事情,paint的任務大概就是把所有的layer繪製到頁面中,這個繪製與canvas的繪製不一樣,canvas的繪製相當於在畫布裡把畫素直接繪製成指定顏色,然後我們直接看到的東西就直接是畫素顏色,而我們這裡說的 Paint
只是把圖層丟到頁面中,最後的繪製,需要交給 Composite執行緒
處理。
最後是 Composite Layers
,由 composite執行緒
進行,這個執行緒在瀏覽器的Renderer程序中,任務是把Paint時候丟上頁面的圖層轉化成點陣圖,最終生成我們肉眼可以看到的影象,所以,真正的繪製,應該是 Composite Layers
過程進行的。
由於 paint
與 composite
解耦,瀏覽器對每一個 layer
都有一個標識,這個標識用來標識該 layer
是否需要重繪,在有CSS規則樹變化的時候,瀏覽器只會對這些被標識的layer進行重繪,用這樣的方式提高瀏覽器的渲染效能。