前言
杭州下雪了,冷到不行,在家躺在床上玩手機,開啟微信進入前端交流群裡日常吹水,看到大佬在群裡發了一篇文章你應該要知道的重繪與重排,文章裡有一段騷操作,就是為了減少重繪與重排,合併樣式操作,這個騷操作成功的引起了我的注意,然後開啟了我的探索。
正文
前言中描述的合併樣式的騷操作是如下:
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
,重新計算css規則樹。 - 進行
Layout
,這裡的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進行重繪,用這樣的方式提高瀏覽器的渲染效能。
最後
前端大法博大精深,越往下學越覺得自己不適合前端!!!彷彿看到自己在從入門到跑路這條路上快走到了終點。。。