1. 程式人生 > >瀏覽器核心、引擎、頁面呈現原理及其優化

瀏覽器核心、引擎、頁面呈現原理及其優化

瀏覽器核心、引擎、頁面呈現原理及其優化

介紹瀏覽器核心、JavaScript 引擎以及頁面呈現原理等基礎知識,同時根據原理提出頁面呈現優化方案。

 

瀏覽器核心

瀏覽器核心又叫渲染引擎,主要負責 HTML、CSS 的解析,頁面佈局、渲染與複合層合成。瀏覽器核心的不同帶來的主要問題是對 CSS 的支援度與屬性表現差異。

現在主流的核心有:Blink、Webkit、Gecko、EdgeHTML、Trident,這裡面有幾個需要注意的地方:

  1. Blink 是在 Webkit 的基礎上的改進,是現在對新特性支援度最好的核心

  2. 移動端基本上全部是 Webkit 或 Blink 核心(除去 Android 上騰訊家的 X5),這兩個核心對新特性的支援度較高,所以新特性可以在移動端大展身手。

  3. Trident 是 IE4+ 的核心,一直持續到 IE11,EdgeHTML 是微軟拋棄 IE 後開發的全新核心

  4. 更多資料請看附錄表格

幾種渲染模式

一般現代瀏覽器都會有以下幾種渲染模式:

  1. 標準模式
  2. 接近標準模式(又稱有限怪異模式)
  3. 怪異模式

不同渲染模式出現的原因

在 IE5 與 NS4 那個年代,瀏覽器大戰,標準未立,Web 則在經歷早期快速地發展。

後來標準逐步建立,新標準的規範與以前 IE5、NS4 的實現存在著不可避免的差異差異,但是此時的網路世界許許多多舊時的頁面正在執行,如果按照新標準的實現來渲染的話會有大量的問題出現。

所以此時大部分現代瀏覽器廠商想到了區別性地使用不同渲染模式來對待這些 Web 頁面。

而 IE 隨著升級,對現代標準的支援也越來越完善,所以 IE 為了正常渲染舊時頁面,支援我們指定哪個版本的 IE 模式來渲染頁面

總結就是:

  1. 怪異模式是 IE5 與 NS4 年代遺留問題的回退方案
  2. 怪異模式在大部分瀏覽器都有部署,並且能在一定的機制下觸發

不同渲染模式的觸發

標準未立之前,HTML 文件是沒有文件頭的,同時在 HTML5 之前的 HTML4/3 的文件頭都有各自的特徵,所以在大部分現代瀏覽器下觸發的機制如下:

  1. 無 DOCTYPE

     頭觸發怪異模式

  2. DOCTYPE 頭不正確(不是 html)也觸發怪異模式

    如:<!DOCTYPE svg>

  3. DOCTYPE 頭為 HTML3 頭觸發怪異模式

  4. DOCTYPE 頭為 HTML4 頭則觸發接近標準模式(或稱有限怪異模式

  5. 常見的 HTML5 DOCTYPE 宣告則使用標準模式

在 IE 下,除了文件頭的差異可以自動觸發渲染模式的選擇,我們還能手動指定(在 IE8+ 適用)使用哪個版本的 IE 渲染模式來渲染我們的頁面(擴充套件閱讀):

1
2
3
4
5
6
7
8
9
<!-- 使用當前作業系統已裝的最新的 IE -->
<!-- chrome=1 是針對雙核瀏覽器使優先使用 Chrome -->
<meta http-equiv="x-ua-compatible" content="ie=edge,chrome=1">

<!-- 使用 IE9 -->
<meta http-equiv="x-ua-compatible" content="ie=9">

<!-- 如果你需要使用 IE5 的怪異模式 -->
<meta http-equiv="x-ua-compatible" content="ie=5">

不同渲染模式的表現差異

怪異模式與標準模式

  1. 怪異模式使用不同於標準的盒模型(也就相當於 IE8+ 下的:box-sizing: border-box
  2. 怪異模式下某些行內(inline)元素的垂直對齊行為表現怪異:怪異模式下對齊圖片至包含它們的盒子的下邊框,而 標準模式圖片對其到父盒的 baseline

接近標準模式(有限怪異模式)與標準模式

主要區別即為上面的第 2 點

JavaScript 引擎

JavaScript 負責 JavaScript 程式碼的解釋與執行,主流的 JavaScript 引擎有:V8、SpiderMonkey、JavaScriptCore、Chakra。

瀏覽器與引擎詳情見附錄表格。

頁面呈現原理

當我們點選一個連結,伺服器將 HTML 程式碼傳輸到我們的瀏覽器,瀏覽器在接收到這份 HTML 程式碼之後是如何一步步將頁面呈現出來的呢?這裡面瀏覽器需要做哪些工作?如何優化呈現的過程提升 Web 應用質量?

六部曲

一個頁面的呈現,粗略的說會經過以下這些步驟:

  1. DOM 樹的構建(Parse HTML)

  2. 構建 CSSOM 樹(Recaculate Style)

    為什麼是 Re-caculate Style 呢?這是因為瀏覽器本身有 User Agent StyleSheet,所以最終的樣式是我們的樣式程式碼樣式與使用者代理預設樣式覆蓋/重新計算得到的。

  3. 合併 DOM 樹與 CSSOM 樹為 Render 樹

  4. 佈局(Layout)

  5. 繪製(Paint)

  6. 複合圖層化(Composite)

    圖層化是自己理解後形象的意譯

其中佈局(Layout)環節主要負責各元素尺寸、位置的計算,繪製(Paint)環節則是繪製頁面畫素資訊,合成(Composite)環節是多個複合層的合成,最終合成的頁面被使用者看到。

六部曲中的阻塞

雖然六部曲看似和諧,分工合作,有序進行。但是實際上這裡面卻是波雲詭譎,風起雲湧,就像平時的工作一樣,看似你和我各司其職,分工明確,但是實際幹起活來卻可能因為某一個人的某一環而阻滯整個進度。

我們來分析這六部曲中存在的阻塞問題:

  1. 當遇到 JavaScript 指令碼或者外部 JavaScript 程式碼時,瀏覽器便停止 DOM 的構建(阻塞 1

    那是否停下 DOM 的構建的同時,立馬就執行 JavaScript 程式碼或者下載外部指令碼執行,其實還是要視情況而定,見 2

  2. 當遇到 <script> 標籤需要執行指令碼程式碼時,瀏覽器會檢查是否這個 <script> 標籤以上的 CSS 檔案是否已經載入並用於構建了 CSSOM,如果 <script> 上部還有 CSS 樣式沒載入,則瀏覽器會等待 <script> 上方樣式的載入完成才會執行該 <script> 內的指令碼(阻塞 2

  3. DOM 樹與 CSSOM 樹的成功構建是後面步驟的根基(同步阻塞

  4. 同時外部指令碼、外部樣式表的下載也是耗費時間較多的點

六部曲之 DOM 樹的構建

image_1ba5aftnnm7i11t8kl716c2164o9.png-96.9kB
瀏覽器構建 DOM 樹可以簡單的總結為以下幾步:

  1. 轉碼(Bytes -> Characters)—— 讀取接收到的 HTML 二進位制資料,按指定編碼格式將位元組轉換為 HTML 字串

  2. Tokens 化(Characters -> Tokens)—— 解析 HTML,將 HTML 字串轉換為結構清晰的 Tokens,每個 Token 都有特殊的含義同時有自己的一套規則

  3. 構建 Nodes(Tokens -> Nodes)—— 每個 Node 都新增特定的屬性(或屬性訪問器),通過指標能夠確定 Node 的父、子、兄弟關係和所屬 treeScope(例如:iframe 的 treeScope 與外層頁面的 treeScope 不同)
    image_1ba5an0vmfsn1kb928a1lv8vkpm.png-49.2kB

  4. 構建 DOM 樹(Nodes -> DOM Tree)—— 最重要的工作是建立起每個結點的父子兄弟關係

在 Chrome 開發者工具下 Timeline 面板的 Parse HTML 階段對應著 DOM 樹的構建

擴充套件閱讀:從Chrome原始碼看瀏覽器如何構建DOM樹

留意這篇文章的這些點:

  1. DOM 構建時對 DOCType 處理
  2. DOCType 的不同或漏缺帶來的文件解析模式(怪異模式、有限怪異模式、標準模式)的影響
  3. 處理開標籤與閉標籤的壓棧、彈棧處理
  4. Chromium 對待自定義標籤的處理
  5. JavaScript 方法查詢 DOM 的過程,使用 ID、類名、複雜選擇器查詢 DOM 的對比

六部曲之 CSSOM 樹的構建

CSSOM 樹的構建 “原料” 的來源有:外部 CSS 檔案、內部樣式、內聯樣式

CSSOM 樹的構建其實是一個 樣式的重新計算 的過程,為什麼是重新計算呢?

使用者代理(即瀏覽器)本身有一套內建樣式表,所以我們最終的 CSSOM 樹其實是使用者代理樣式與頁面所有樣式的重新計算

所以在 Chrome 瀏覽器開發者工具的 Timeline 面板下,CSSOM 樹的構建對應的是 Recalculate Style 階段

與 DOM 樹的構建過程相似,CSSOM 的構建也要經歷以下過程:
image_1ba7m6adr1h52bm2mblup214t59.png-10.3kB

最終構建的 CSSOM 樹大致如下:
image_1ba7m7h67t731ikr1461topakgm.png-42.2kB

六部曲之渲染樹的構建

  1. DOM 樹與 CSSOM 樹融合成渲染樹

  2. 渲染樹只包括渲染頁面需要的節點

    排除 <script> <meta> 等功能化、非視覺節點
    排除 display: none 的節點

image_1ba7mjun4h4l123b1smn13indhg13.png-116.2kB

六部曲之佈局

Layout 階段做的工作:確定頁面各元素的位置、尺寸。

Layout 在 Chrome 開發者工具 Timeline 面板中被歸併到 Paint 階段

當元素某些樣式變更/JavaScript 執行某些樣式請求,會導致 Layout trashing,又叫做迴流(Reflow)。

六部曲之繪製

一旦佈局(Layout)步驟完成,瀏覽器便觸發 “Paint Setup” 與 “Paint” 事件(渲染引擎底層概念),執行 paint 操作,結合渲染樹與佈局資訊繪製實際畫素

注:在 Timeline 工具內,Layout 與 Paint 兩個過程被統一歸併到 Paint 階段。

六部曲之複合圖層化

在很多情況下,我們不會將複合圖層化歸入頁面呈現的必要過程。圖層化是瀏覽器為了充分利用已有渲染成果(快取渲染成果),最小化 GPU 運算,將“髒區”提升為複合圖層,隔離變化影響的操作。

見 連結

頁面效能優化

知道了頁面渲染的原理,那麼我們也就得到了頁面效能優化的依據。提煉六部曲中每一步的優化空間,針對六部曲中的每一步提出針對性的優化方案也就能達到我們最終的優化目的。

優化不可避免的阻塞:優化關鍵呈現路徑

關鍵呈現路徑裡的一些概念

  • 關鍵資源:可能阻止網頁首次呈現的資源。
  • 關鍵路徑長度:即往返過程數量,或提取所有關鍵資源所需的總時間。
  • 關鍵位元組:實現網頁首次呈現所需的總位元組數,是所有關鍵資源的傳輸檔案大小總和。 帶有一個 HTML 網頁的首個示例包含一項關鍵資源(HTML 文件),關鍵路徑長度也與 1 個網路往返過程(假設檔案較小)相等,而且總的關鍵位元組數正好是 HTML 文件本身的傳輸大小。

優化關鍵呈現路徑的指導原則

  • 儘量減少關鍵資源數量。
  • 儘量減少關鍵位元組數。
  • 儘量縮短關鍵路徑的長度。

優化關鍵呈現路徑常規步驟

  1. 分析和描述關鍵路徑:資源數量、位元組數、長度。
  2. 儘量減少關鍵資源數量:刪除相應資源、延遲下載、標記為非同步資源等等。
  3. 優化剩餘關鍵資源的載入順序:你需要儘早下載所有關鍵資源,以縮短關鍵路徑長度。
  4. 儘量減少關鍵位元組數,以縮短下載時間(和往返次數)。

優化關鍵呈現路徑的具體建議

  1. 檔案合併、壓縮

  2. 推薦使用非同步(async) JavaScript 資源,或使用延遲(defer)執行的 JavaScript

  3. 一般 <script> 指令碼的靠後書寫

  4. 避免執行時間長的 JavaScript,耗時任務的拆分,chunk 化執行

    例如:使用定時器將大任務拆分為小任務,使得瀏覽器得到空隙做其他事情。

  5. 避免使用 CSS import

  6. 內聯、內部化阻止呈現的 CSS

    一般不採用,百度、Google 這樣的極度重視效能與體驗的服務才可能這樣做。

針對複合圖層化的優化

因為瀏覽器有圖層化這個機制,那麼我們就搞懂它並充分利用吧。

複合圖層化機制

某些屬性的變更(transformopacity)滿足以下條件:

  • 不影響文件流。
  • 不依賴文件流。
  • 不會造成重繪。

那麼這些屬性變更時就需要一種機制:機制需要能將屬性變更的部分與頁面其他部分隔離開來,其他部分已經渲染完好進行快取,變更的部分在單獨的圖層上進行,然後對快取的部分與變更的圖層進行合成。

所以圖層化的關鍵字:快取隔離圖層合成

使用 transform 與 opacity 進行屬性變更是經典的符合圖層化方法,以下是其他會提升元素為複合圖層的場景

  1. 3d 或透視變換 CSS 屬性,例如 translate3dtranslateZ 等等(JS 一般通過這種方式,使元素獲得複合圖層)
  2. <video> <iframe> <canvas> <webgl> 等元素。
  3. 混合外掛(如flash)。
  4. 元素自身的 opacity 和 transform 做 CSS 動畫。
  5. 擁有 CSS Filter 的元素。
  6. 使用 will-change 屬性。
  7. position:fixed
  8. 元素有一個 z-index 較低且包含一個複合層的兄弟元素(換句話說就是該元素在複合層上方)

圖層化的優勢

很容易看出來:充分利用快取、隔離的思想,無需像迴流、重繪那麼大效能(GPU、CPU)開支,圖層化能帶來動畫效能的提升。

圖層化的潛在問題 —— 記憶體開銷

那麼圖層化的弊端在哪裡呢?

因為圖層化的存在,每個圖層對需要在記憶體中儲存該圖層相關的資訊,當圖層太多會造成記憶體開銷過大的情況(如下圖)。

image_1baah8s0vcol3464t21foe1sb91g.png-31.3kB

同樣表現的頁面,單圖層與多圖層的記憶體開銷

因為開銷,所以節制

記憶體開銷在桌面端可能還能接受,但是在資源有限的移動端,複合圖層過多便可能導致記憶體開支過大,頁面反而變得停滯、卡頓,甚至瀏覽器假死,系統無法正常執行。

針對迴流的優化

  1. CSS Triggers

    1. CSS3 > JavaScript
    2. 屬性變更優先考慮順序(效能表現排序)
      1. transfromopacity
      2. background-color 等
      3. position - top bottom left right
      4. width height 等
      5. marginpaddingborder
  2. What forces layout

    JavaScript 存在這樣的機制:當連續有大量 DOM 樣式的操作時,出於效能考慮,防止零碎變更導致頻繁的迴流、重繪,會盡可能地將這些操作先快取起來,然後一次性地變更。這個機制我們難以察覺但是確實存在。

    然而當我們進行某些 DOM 樣式的讀、寫時,出於時效性的考慮,則會立即觸發瀏覽器迴流、重繪以返回正確、合理的值。

其他優化技巧

節流函式

已經比較明白了,那就略吧  參考

惰性載入函式

也已經比較明白,也略吧 參考

重任務分片多幀

例項講解

Timeline 工具

使用 Timeline 工具我們能做以下事:

  1. 頁面渲染幀率分析,得到異常幀資訊
  2. 各類檔案執行耗時/耗資源分析
  3. 檔案等待 - 下載時間
  4. 頁面呈現期間的事件列表
  5. 某個幀/某個事件的詳細資訊分析

image_1ba7qi29r1b5f12c7tvh1muh9rm.png-524.5kB

理解 Timeline 工具使用,讀懂這圖就夠了

image_1ba7rnvjs4gb1l35b6a1d9r3db34.png-28.7kB

常用事件,更多事件見[擴充套件閱讀](#擴充套件閱讀)

而我們在日常開發中,用 Timeline 最多的場景是:

  1. 編寫動畫,Debug 不流暢的異常幀,針對性優化(見本章擴充套件閱讀一節)
  2. 偵測 重新計算樣式重新佈局重繪重新合成 等事件,針對性優化
  3. 瞭解 JavaScript 函式呼叫棧以及函式呼叫帶來的迴流/重繪事件資訊
  4. 檢視在某個事件中程式碼的執行耗時(點選上圖 Main 部分,點選下方常駐 Panel 內與檔案相關的可點選連結)
    image_1ba7rah2h136v1keujkjbn7pmv2a.png-152.8kB

讓你的 Timeline 除錯更強大

  1. 選擇性地開啟以下開關
    image_1ba7qso561sv3ep6lur1civ1vba13.png-49.2kB

  2. 開啟開發者工具實驗性特性開關
    image_1ba7r0k4u12du17j819kkvcqgfv1t.png-405.1kB

    image_1ba7r0cuochpk331s6810jq1t8n1g.png-60.6kB

擴充套件閱讀

Timeline 官方簡介
Timeline 事件參考
推薦:Timeline 進行幀分析,避免頁面卡頓

附錄

瀏覽器核心與 JS 引擎一覽

瀏覽器/RunTime 核心(渲染引擎) JavaScript 引擎
Chrome Blink(28~)
Webkit(Chrome 27)
V8
FireFox Gecko SpiderMonkey
Safari Webkit JavaScriptCore
Edge EdgeHTML Chakra(for JavaScript)
IE Trident Chakra(for JScript)
PhantomJS Webkit JavaScriptCore
Node.js - V8