瀏覽器渲染原理分析
阿新 • • 發佈:2020-12-15
瀏覽器渲染原理分析
一、前景知識
- 瀏覽器引擎:瀏覽器有兩個引擎,GUI渲染引擎和 js 引擎,這兩個引擎的工作的執行緒是互斥的,具體原因後面再說。
- 佈局和繪製:生成渲染樹後,瀏覽器計算元素大小和位置來 flow 生成佈局,然後進行 paint 繪製,這一步合起來,叫做渲染 render
- 重繪:對 DOM 進行樣式修改,但是沒有影響它的幾何屬性時,瀏覽器會進行重新繪製 repaint
- 迴流:DOM 的幾何屬性發送變化時(寬高、位置、層級),瀏覽器需要進行**重新佈局 reflow **
- 迴流一定引發重繪,重繪不一定引發迴流,迴流成本比重繪要高得多。
二、主要流程
1. 不考慮 script 時
- 下載 html 檔案,開始解析,解析的同時開始構建 DOM 樹(值得一提的說,這是一個深度優先遍歷的過程,即先構建當前節點的子節點,再去構建下一個兄弟節點)
- 如果解析的過程遇到了 css 檔案的引用,會去下載 css 檔案,並解析成 css 規則樹。(值得一提的是,Dom 樹的構建和 css 規則樹的構建互不影響,二者是並行的)
- 兩者都解析完畢後,DOM樹 和 css 樹會生成渲染樹,進行頁面的渲染(生成佈局、繪製佈局)
2. 考慮 script 時
- 如果解析 html 的時候,遇到了 script 標籤,渲染引擎就會交出控制權給 js 引擎,去進行js的解析。(猜測:被打斷前,如果css規則樹已經存在,那麼會進行一次渲染)這裡的原因是因為瀏覽器需要維護一個相對穩定的DOM結構,如果兩個引擎同時執行那麼 js 裡面進行 Dom 操作會把 DOM 結構給打亂。所以,如果想首屏渲染得快,就應該把 script 標籤放到 body 後面
- 如果 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)
- 上述事件觸發流程:
- readystatechange 事件(readyState == loading)
- readystatechange 事件 (readyState == interactive)
- DOMContentLoaded 事件(readyState == interactive)
- readystatechange 事件(readyState == complete)
- load 事件(readyState == complete)
2. script 載入的 async(非同步下載) 和 defer(延遲執行)
- 正常:讀到立即載入
- async 非同步下載:非同步載入,載入完畢立即執行,會阻塞 load 事件。可能在 DOMContentLoaded 觸發之前或之後執行,但一定會在 load 之前執行。
- defer 延遲執行:非同步載入,載入完畢並且 html 文件解析完畢後執行,執行完畢後才觸發 DOMContentLoaded 事件。
- 在載入多個 js 檔案的時候,async 是無順序的載入,而 defer 是有順序的載入。HTML5標準是這樣說的,但在現實當中,延遲指令碼並不一定會按照順序執行,也不一定會在DOMContentLoaded 事件觸發前執行
(參見《JavaScript高階程式設計》2.1節 <script>元素)
3. 頁面效能優化
- 減少迴流、重繪:
- 不要把DOM的讀寫操作放到同一個語句裡。瀏覽器其實對樣式修改做了優化,瀏覽器會盡量把所有變動放到一個佇列裡一次性執行,比如連續修改同一個屬性兩次只會引發一次渲染,但是如果對同一個屬性進行修改了又讀然後又修改,就會引發兩次渲染,因為第一次修改後的再讀迫使頁面進行重新渲染。
- 讀取 offsetxxx、scrollxxx、clientxxx 會引發瀏覽器迴流,儘量減少使用
- table 元素的迴流和重繪成本高於 div
- 樣式表越複雜,重繪和迴流成本越高
- DOM 元素層級越高,重繪和迴流成本越高
- 使用 cloneNode() 方法複製一個離線 DOM 出來,進行多次 DOM 操作後再插入
- 將元素設為 display: none(一次重新渲染),進行多次DOM操作再恢復顯示(一次重新渲染),用 2 次重新渲染取代多次渲染
- 動畫幀優化
- Web Worker,將與 UI 渲染無關的計算任務都放到 Worker 執行緒裡面
- window.requestAnimationFrame() 方法,將程式碼放到下次重新渲染時執行。通過遞迴呼叫自身,就可以控制每一幀的樣式。適應場景:樣式讀寫分離、頁面滾動事件(scroll)的監聽函式、連續動畫
- window.requestIdleCallback() 方法,它指定只有當一幀的末尾有空閒時間,才會執行回撥函式。
- 檔案載入優化:
- script 放到 body 後,或者指定 defer 屬性或 async 屬性進行非同步載入,避免影響 html 的解析,進而優化首屏渲染。
- link 的 css 檔案可以指定 rel = preload,指明這個檔案的優先順序,進而控制最優的資源載入順序,提高渲染效能。
參考文獻:
Nicholas C.Zakas——《JavaScript高階程式設計》
阮一峰——網頁效能管理詳解
ljianshu——深入淺出瀏覽器渲染原理
ljianshu——從URL輸入到頁面展現到底發生什麼?