瀏覽器渲染流程的觀點下,前端性能優化的原則
前言
在瀏覽器輸入URL後發生什麼事一文中,我們可以得知在瀏覽器從 Network Process 接收到HTML數據後,會交給 Render Process 來進行渲染;而在前端工程角度下的性能優化,便是考慮在 Render Process 渲染中、如何對程式碼進行調優,進而加快渲染速度。因此,了解 Render Process 是如何對頁面進行渲染,在前端性能優化中是十分重要的!
Browser Rendering pipeline(渲染流水線)
在瀏覽器的渲染流水線中,基本上可以分為以下步驟:
- 建構DOM樹
- 計算樣式
- 佈局
- 分層
- 繪製
- 合成(分塊、栅格化、合成)
而基於pipeline
的結構、也就是一個步驟的輸出是下一個步驟的輸入,因此,只要關注每個步驟輸入了什麼、然後輸出了什麼到下一個步驟,就可以很清楚的了解整個 Render Pipeline 的流程:
建構DOM樹
它的輸入是一個最簡單的HTML文件,然後經由Render Process 中的
HTMLParse
模塊,解析成樹狀結構的DOM Tree,然後輸出到下一階段—計算樣式。計算樣式
此階段的目的是要計算出每個DOM節點的樣式。Render Process接收到CSS文件時(無論是通過
<style></style>
、<link ref=""/>
還是inline的方式)後,會將其轉換為styleSheets
以讓瀏覽器理解。然後計算每個DOM的樣式(如繼承、單位統一等等),保存在ComputedStyle
結構中,輸出到下一階段—佈局佈局
結合
DOM Tree
和ComputedStyle
,然後摒除一些不可見的元素(如<head>
,display:none
等),接著計算各個DOM在頁面中的位置,形成Layout Tree輸出給分層階段分層
頁面中有一些複雜的畫面效果(如css中的
opacity
、z-index
、3D效果等等),因此Render Process還需要為一些節點生成專門的圖層(layer),形成Layer Tree
,然後輸出給繪製階段繪製
在這一階段,Render Process是基於
Layer Tree
,來生成相對應的繪製指令
(白話文來說就像是「在座標(100,30)的地方畫藍色」等等),將這些指令列表提交到合成線程(threading)中。合成線程
將繪製指令基於(viewport)分成一塊塊的圖塊(讓瀏覽器不用一下全部渲染),然後再基於該圖塊的附近優先生成位圖。這個過程通常會讓GPU Process參與來加速渲染流程。接著,合成線程發送繪製圖塊命令
DrawQuad
給Browser Process.最後,Browser Process根據接收到的 DrawQuad 消息來生成頁面,顯示到顯示器上。
基於渲染流水線的性能優化
了解了渲染流水線的大致過程,便可以如庖丁解牛般,對每個可以操作的步驟來進行優化、進而達到全面的性能優化!而以瀏覽器的角度出發,可以把性能優化再分為 加載階段的優化 和 交互階段的優化:
加載階段的優化
圖片、影片等資源,是不會阻塞頁面的首次渲染,但是Javascript文件和CSS文件會。因為在建構DOM樹的時候,HTMLParse遇到<script>
標籤後,會暫停渲染並執行該script;而在構造Layout Tree時會需要CSS文件。(另外,如果JS文件中有會修改到CSS屬性,也必須等待CSS文件加載完後、構建CSSOM後才能執行該JS script)
- Javascript文件優化:
- 使用CDN加速(因為同一個站點,Network Process能給執行的TCP有限(6個?);而有CDN的話就可以規避這個侷限)
- 壓縮JS文件體積(webpack 插件)
- 如果JS文件中沒有操作DOM的script,可以用異步加載(
defer
和async
。兩者的差別是:async是文件加載完後立刻執行,而defer是要等DOM建立完之後(DOMContentLoaded
)執行)
CSS文件優化
- 大的CSS文件,可以拆分成不同用途的CSS文件,然後在特定情況下加載特定CSS文件
利用
分層
技術來優化。如果直接修改CSS,如JS對某個DOM進行幾何形狀變化、透明度變化或縮放操作等等,會影響整個渲染流水線(Reflow/Repaint);- Reflow—因為修改了DOM的幾何屬性如大小,所以瀏覽器要重新計算樣式、佈局、分層,等於要從
計算樣式
的階段重新開始渲染流水線。 - Repaint—修改了DOM的顏色,雖然也要重新計算樣式、但不用再重新佈局和分層,所以會在渲染流水線中跳過這兩個階段,但還是耗性能。
但如果在css文件中加上
will-change
屬性來告訴Render Process這個元素會做特殊變化,那麼Render Process會將這個變化放到合成線程中去執行,而合成線程不會造成整個渲染流水線的 Reflow 或 Repaint.box { will-change: transform, opacity, background-color; }
- Reflow—因為修改了DOM的幾何屬性如大小,所以瀏覽器要重新計算樣式、佈局、分層,等於要從
交互階段的優化
在交互階段,是頁面加載完成到使用者交互的過程,這裏最主要的就是Javascript
- 減少Javascript的執行時間
- 將一個大型的Javascript任務拆解成多個小任務。如果一個JS任務長期霸佔main thread,會讓用戶體驗變很差。
- 如:spa router 的 lazy-loading
- 使用web workers,將不會和DOM相關、但又耗時的JS script放到 web workers來執行。
- 將一個大型的Javascript任務拆解成多個小任務。如果一個JS任務長期霸佔main thread,會讓用戶體驗變很差。
- 避免強制同步佈局和佈局抖動
- 強制同步佈局,簡單來說就是在一次操作中,既要計算樣式又要改變佈局,如在
function foo
中,要把.testDom
新增子節點、又要查詢.testDom
的offsetHeight - 佈局抖動,最有可能發生的情況是在一個函數中多次強制同步佈局
- 虛擬DOM,在某種程度上也是解決了JS多次操作DOM的問題。在JS環境中先修改完虛擬DOM,然後在渲染成真實DOM,這樣就只要修改一次就好。
- 強制同步佈局,簡單來說就是在一次操作中,既要計算樣式又要改變佈局,如在
- 避免頻繁垃圾回收
- 像是在一段for循環,如果有共用變量或對象的話,那就在for循環外聲明而不是循環內,這樣就不用每結束一次循環就要回收一次。
ChangeLog
- 20221026 - 初稿
Ref
- 極客時間:《瀏覽器原理與實踐》