關於迴流與重繪優化的探索

麥樂丶發表於2018-12-09

前言

杭州下雪了,冷到不行,在家躺在床上玩手機,開啟微信進入前端交流群裡日常吹水,看到大佬在群裡發了一篇文章你應該要知道的重繪與重排,文章裡有一段騷操作,就是為了減少重繪與重排,合併樣式操作,這個騷操作成功的引起了我的注意,然後開啟了我的探索。

原文地址

正文

前言中描述的合併樣式的騷操作是如下:

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;

首先回顧一下瀏覽器渲染頁面的流程:

  1. 請求拿到html報文。
  2. 同時解析生成CSS規則樹和DOM樹。
  3. 合併CSS規則樹和DOM樹,生成render樹。
  4. 渲染程式根據render樹進行Layout。
  5. 繪製paint頁面。

然後在看看上面的過程,可以容易看出,

  1. 首先,Recalculate Style,重新計算css規則樹。
  2. 進行Layout,這裡的Layout可以理解成迴流,重新計算每個元素的位置。
  3. Update Layer Tree,字面意思理解,更新層級樹。
  4. Paint,繪製頁面,在這裡可以理解成重繪。
  5. 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過程進行的。

由於paintcomposite解耦,瀏覽器對每一個layer都有一個標識,這個標識用來標識該layer是否需要重繪,在有CSS規則樹變化的時候,瀏覽器只會對這些被標識的layer進行重繪,用這樣的方式提高瀏覽器的渲染效能。

最後

前端大法博大精深,越往下學越覺得自己不適合前端!!!彷彿看到自己在從入門到跑路這條路上快走到了終點。。。

參考

相關文章