1. 程式人生 > 實用技巧 >瀏覽器渲染原理分析

瀏覽器渲染原理分析

瀏覽器渲染原理分析

一、前景知識

  • 瀏覽器引擎:瀏覽器有兩個引擎,GUI渲染引擎和 js 引擎,這兩個引擎的工作的執行緒是互斥的,具體原因後面再說。
  • 佈局和繪製:生成渲染樹後,瀏覽器計算元素大小和位置來 flow 生成佈局,然後進行 paint 繪製,這一步合起來,叫做渲染 render
  • 重繪:對 DOM 進行樣式修改,但是沒有影響它的幾何屬性時,瀏覽器會進行重新繪製 repaint
  • 迴流:DOM 的幾何屬性發送變化時(寬高、位置、層級),瀏覽器需要進行**重新佈局 reflow **
  • 迴流一定引發重繪,重繪不一定引發迴流,迴流成本比重繪要高得多。

二、主要流程

1. 不考慮 script 時

  1. 下載 html 檔案,開始解析,解析的同時開始構建 DOM 樹(值得一提的說,這是一個深度優先遍歷的過程,即先構建當前節點的子節點,再去構建下一個兄弟節點)
  2. 如果解析的過程遇到了 css 檔案的引用,會去下載 css 檔案,並解析成 css 規則樹。(值得一提的是,Dom 樹的構建和 css 規則樹的構建互不影響,二者是並行的
  3. 兩者都解析完畢後,DOM樹 和 css 樹會生成渲染樹,進行頁面的渲染(生成佈局、繪製佈局)

2. 考慮 script 時

  1. 如果解析 html 的時候,遇到了 script 標籤,渲染引擎就會交出控制權給 js 引擎,去進行js的解析。(猜測:被打斷前,如果css規則樹已經存在,那麼會進行一次渲染)這裡的原因是因為瀏覽器需要維護一個相對穩定的DOM結構,如果兩個引擎同時執行那麼 js 裡面進行 Dom 操作會把 DOM 結構給打亂。所以,如果想首屏渲染得快,就應該把 script 標籤放到 body 後面
  2. 如果 script 打斷 html 解析時,同時有 css 物件模型還沒有被構建完成,那麼 js 的解析執行也會被打斷。因為 js 可能會訪問 css 物件模型,而 css 物件模型必須是一個完整的物件模型才能被訪問,所以瀏覽器又會先去下載和構建 css 物件模型,然後再繼續解析執行 js,最後再繼續構建 DOM

三、拓展

1. 頁面載入的事件

  • readyState 表示頁面載入情況,有三種取值:
    • loading:html 文件正在載入解析
    • interactive:html 文件已經載入和解析完畢,子資源(images、css檔案)仍在載入
    • complete:html 文件和全部子資源都載入完畢了
  • readystatechange 事件:readyState 改變時觸發
  • DOMContentLoaded 事件:readyState 為 interactive 時觸發
  • load 事件:readyState 為 complete 時觸發(window.onload)
  • 上述事件觸發流程:
    1. readystatechange 事件(readyState == loading)
    2. readystatechange 事件 (readyState == interactive)
    3. DOMContentLoaded 事件(readyState == interactive)
    4. readystatechange 事件(readyState == complete)
    5. load 事件(readyState == complete)

2. script 載入的 async(非同步下載) 和 defer(延遲執行)

  • 正常:讀到立即載入
  • async 非同步下載:非同步載入,載入完畢立即執行,會阻塞 load 事件。可能在 DOMContentLoaded 觸發之前或之後執行,但一定會在 load 之前執行。
  • defer 延遲執行:非同步載入,載入完畢並且 html 文件解析完畢後執行,執行完畢後才觸發 DOMContentLoaded 事件
  • 在載入多個 js 檔案的時候,async 是無順序的載入,而 defer 是有順序的載入。HTML5標準是這樣說的,但在現實當中,延遲指令碼並不一定會按照順序執行,也不一定會在DOMContentLoaded 事件觸發前執行(參見《JavaScript高階程式設計》2.1節 <script>元素)

3. 頁面效能優化

  1. 減少迴流、重繪:
    1. 不要把DOM的讀寫操作放到同一個語句裡。瀏覽器其實對樣式修改做了優化,瀏覽器會盡量把所有變動放到一個佇列裡一次性執行,比如連續修改同一個屬性兩次只會引發一次渲染,但是如果對同一個屬性進行修改了又讀然後又修改,就會引發兩次渲染,因為第一次修改後的再讀迫使頁面進行重新渲染。
    2. 讀取 offsetxxx、scrollxxx、clientxxx 會引發瀏覽器迴流,儘量減少使用
    3. table 元素的迴流和重繪成本高於 div
    4. 樣式表越複雜,重繪和迴流成本越高
    5. DOM 元素層級越高,重繪和迴流成本越高
    6. 使用 cloneNode() 方法複製一個離線 DOM 出來,進行多次 DOM 操作後再插入
    7. 將元素設為 display: none(一次重新渲染),進行多次DOM操作再恢復顯示(一次重新渲染),用 2 次重新渲染取代多次渲染
  2. 動畫幀優化
    1. Web Worker,將與 UI 渲染無關的計算任務都放到 Worker 執行緒裡面
    2. window.requestAnimationFrame() 方法,將程式碼放到下次重新渲染時執行。通過遞迴呼叫自身,就可以控制每一幀的樣式。適應場景:樣式讀寫分離、頁面滾動事件(scroll)的監聽函式、連續動畫
    3. window.requestIdleCallback() 方法,它指定只有當一幀的末尾有空閒時間,才會執行回撥函式。
  3. 檔案載入優化:
    1. script 放到 body 後,或者指定 defer 屬性或 async 屬性進行非同步載入,避免影響 html 的解析,進而優化首屏渲染。
    2. link 的 css 檔案可以指定 rel = preload,指明這個檔案的優先順序,進而控制最優的資源載入順序,提高渲染效能。

參考文獻:
Nicholas C.Zakas——《JavaScript高階程式設計》
阮一峰——網頁效能管理詳解
ljianshu——深入淺出瀏覽器渲染原理
ljianshu——從URL輸入到頁面展現到底發生什麼?