淺析渲染引擎與前端優化

hanyuxinting發表於2016-12-07

本文主要是兩方面內容:

  • 淺析瀏覽器核心的工作原理(以 WebKit 2 為例)。
  • 淺析由瀏覽器核心想到的前端優化,或者說前端優化規則是從哪兒來的。

大家知道,大部分的 WEB 頁面依託瀏覽器呈現,而瀏覽器能夠將頁面展示出來,基本依賴於瀏覽器的核心,即渲染引擎。今天以 Chrome 瀏覽器的核心 WebKit(更確切是 WebKit 分支 Blink,以下統稱為 WebKit )為例,對渲染引擎如何展示頁面做個簡單、全面的瞭解。

瀏覽器的渲染引擎及其依賴模組

渲染引擎主要是將 WEB 資源如 HTML、CSS、圖片、JavaScript等經過一系列加工,最終呈現出展示的影像。渲染引擎主要包含了對這些資源解析的處理器,如 HTML 直譯器、CSS 直譯器、佈局計算+繪圖工具、JavaScript 引擎等。為了更好地呈現渲染效果,渲染引擎還會依賴網路棧、快取機制、繪圖工具、硬體加速機制等。

瀏覽器的渲染過程

瀏覽器的渲染過程,主要包括兩大部分:網頁資源載入過程渲染過程

上圖將整個網頁渲染的過程做了大致的剖析。以下我們按照資料流向,逐一詳細剖析每個過程。

一、域名解析 DNS

當我們在瀏覽器中輸入 URL 後,瀏覽器首先會進行域名解析。一般情況下,一次 DNS 域名解析大概需要 60-120 ms,一次 TCP 的三次握手需要 1.5 個 RTT(round-trip time)。WebKit 的方案是 採用 DNS 預取技術和 TCP 預連線技術。

DNS 預取技術利用現有 DNS 機制,提前解析網頁中可能的網路連線。即對使用者瀏覽網頁中存在的連結,用較少的 CPU 和網路頻寬來解析這些連結的域名或 IP 地址;等使用者單擊連結時,就會節省時間~ 特別是域名解析慢的時候~

同樣,在位址列輸入連結時,候選項也會被默默地執行 DNS 預取~。在 DNS 預取後,會預先建立 TCP 連線。

對此前端優化建議:

  • 在頁面中指定預取域名:<link rel=”dns-prefetch” href=”http://this-is-a.com”>
  • 大資料分析,推測使用者可能點選的連結,提前預取。
  • 減少頁面中的域名數量,可以直接減少DNS的請求。

二、SPDY 和 HTTP2

因為請求帶來的 TCP 三次握手的 1.5 RTT 延遲,Google 引入 SPDY,嘗試解決HTTP的延遲和安全性(HTTP 明文方式)問題。不過,SPDY 促使了 HTTP2.0 的誕生後,自己也不再更新,逐步退出。

SPDY 基於 SSL 之上,輕鬆相容 HTTP 新老版本。其優勢如下:

  • 多路複用。一個 TCP 連線傳輸多個資源。減少 TCP 連線成本。
  • 不同資源,不同優先順序。比如優先載入首屏。
  • Header 頭壓縮。減少傳送的位元組數。SPDY 對 Header 壓縮率可高達 80%。

SPDY 開拓了 HTTP 新局面,秒殺我們太多的前端優化工作,從本質上提升了頁面載入速度。但我們前端優化的工作還是不能偏廢。向著繼續減少請求減少 TCP 連線建立的路上,讓我們繼續。

  • 合併資源,如 combo 合併 JavaScript 檔案、CSS 檔案,利用 sprite 合併圖片,圖片地圖等;
  • 當頁面資源較小時,可直接放頁面中,如小圖可使用 Base64 編碼格式引入。甚至一些基礎樣式,或首屏依賴樣式,都可以放在頁面中;
  • 資源壓縮技術。如 Gzip 等。主要是對響應資料的壓縮~
  • 精簡 JavaScript 和 CSS 程式碼。減少無用的空格。壓縮混淆~
  • 避免連結重定向、避免錯誤的連結請求。建立多次連結、多次 DNS 解析,阻礙 DNS 預取技術。及時更新掉你頁面中沒有價值的連結吧。

三、資源載入

域名解析完,TCP 連線也建立起來後,資源載入器就開始工作了。

資源及資源載入器

資源包括:HTML、JavaScript、CSS 樣式表、圖片、SVG、字型檔案、視訊音訊等。資源載入器有三種:

  1. 特定載入器,只載入某一種。如ImageLoader類。
  2. 快取機制的資源載入器。特定載入器通過它查詢是否有快取資源,屬於 HTML 的文件物件。
  3. 通用的資源載入器。在WebKit需要從網路或檔案系統獲取資源時使用。只負責獲取資源的資料,被所有特定資源載入器共享。

資源載入的過程

在 WebKit 中,資源都以 CachedResource 為基類,以 Cached 為字首,體現了瀏覽器的快取機制。即請求資源時,瀏覽器會先看快取中有沒有這個資源,然後再決定是否向伺服器發出請求。

這引出兩個問題,首先,快取資源的生命週期

瀏覽器快取不會無限增大,快取池中的資料必然出現更替,WebKit 採用 LRU 最近最少使用演算法更新快取池資料。WebKit 遵循 HTTP 協議,當頁面重新整理時,判斷資源是否在資源池。若存在,則附上該資源在本地的一些資訊(如修改時間等),傳送 HTTP 請求給伺服器,伺服器根據資訊作出判斷,若資源沒更新則網路狀態為 304,利用現有資源;否則執行資源載入過程。

其次,資源載入過程

資源池中沒有該資源時,執行載入過程。WebKit 可以並行(多執行緒)下載普通資源和 JavaScript 資源。在當前主執行緒被阻塞時,WebKit 會啟動另一個執行緒去遍歷後邊的網頁,收集需要的資源 URL再發請求,避免阻塞。

基於資源載入,前端優化建議:

利用快取機制,快取常用且短時期內不會變更的資源,或給資源設定過期時間。

比如設定 ETag/Last-Modified 和 Expires/Cache-Control
Expires/Cache-Control 兩者作用一致,指明資源有效期,如果本地快取還在有效期內,瀏覽器直接使用本地快取,不再傳送請求。兩者同時配置時,Cache-Control 高於 Expires。

配置 ETag/Last-Modified 後,瀏覽器再次訪問 URL 時,還會向伺服器傳送請求,確認檔案是否已修改,沒修改則伺服器返回304,瀏覽器直接從本地快取獲取資料;修改過則伺服器返回資料給瀏覽器。兩者同時配置,伺服器會優先檢測 ETag,一致才會繼續檢測 Last-Modified。兩者同時配置,可以使伺服器更準確的判斷瀏覽器是否已有需要的快取資料。

ETag/Last-Modified 和 Expires/Cache-Control 兩對都設定時, Expires/Cache-Control 優先順序更高。所以,只要本地快取在有效期內,就不會傳送請求。但頁面 F5 重新整理和強刷時,快取將失效。

鑑於資源下載中可能被阻塞,將 JavaScript 檔案放置頁面下方。JavaScript 資源就是阻塞主執行緒的那個,而重建一個執行緒也是需要時間滴,所以把 JavaScript 扔最後吧~ 但 JavaScript 資源並不影響之前資源的載入和 DOM 樹的構建。

四、從 URL 到 DOM 樹的構建

當我們拿到頁面所需的資源後,渲染引擎便啟動 HTML 直譯器,對獲取的資源進行解析處理。網頁程式碼(位元組流)經過詞法分析器解碼,再由語法分析器解釋成詞語 Token,並構建成節點 Node,直到最終構建成一棵 DOM 樹。

期間,當節點為 JavaScript 節點時,將啟動 JavaScript 引擎,這時將阻塞 DOM 樹的構建。因為 JavaScript 執行過程中, JavaScript 很可能會對 DOM 樹進行讀寫操作。直到 JavaScript 執行完畢, DOM 樹才會恢復構建。

其他資源並不影響 DOM 樹的構建。

前端優化中,建議將 CSS 檔案放在頁首,以便構建 DOM 樹;而將 JavaScript 檔案儘量放在頁面下方,防止阻塞構建 DOM 樹;而 JavaScript 的 onload 事件裡,不要寫太多影響首屏渲染的、操作 DOM 樹的 JavaScript 程式碼。

另外強調一下:

DOMContentLoaded: DOM 樹構建完;
DOM 的onload事件: DOM 樹構建完且網頁依賴的資源都載入完了~

五、網頁排版過程:由 DOM 樹到構建 RenderLayer 樹

這一過程,就像是頁面的排版過程。它通過 CSS 樣式資訊,對 DOM 樹進行排版,形成 RenderObject 樹及 RenderLayer 樹。

在 DOM 樹構建完成後,WebKit 為 DOM 樹節點構建 RenderObject 物件。WebKit 將根據盒模型計算節點的位置、大小等樣式資訊(即佈局計算或排版),並將這些資訊儲存到對應的 RenderObject 物件。

1. CSS直譯器

CSS解釋過程,是從 CSS 字串經過 CSS 直譯器(CSSParser、CSSGrammer)處理後,變成渲染引擎的內部樣式規則表示的過程。樣式規則是直譯器的輸出結構,是樣式匹配的輸入資料。

具體過程:WebKit 在渲染元素時,CSS 直譯器獲取樣式資訊,返回匹配好的結果樣式資訊。每個元素可能需要匹配不同來源的規則,依次是使用者代理(瀏覽器)規則集合、使用者規則集合和HTML頁面中包含的自定義規則集合。三者匹配方式類似。

對於每個規則集合,先查詢 ID 規則,檢查有無匹配的規則,然後依次檢查型別規則、標籤規則等。匹配好的規則,儲存到匹配結果中。WebKit 對這些規則進行排序。對於元素需要的樣式屬性,WebKit 選擇從高優先順序規則中選取,並將樣式屬性值返回。

2. 渲染基礎:RenderObject 樹

DOM 樹經過佈局計算、CSS parse 後,將樣式資訊儲存在 RenderObject 物件中,並構建成 RenderObject 樹。同時,WebKit 會根據網頁的層次結構建立 RenderLayer 樹,完成繪圖上下文。DOM 樹、Render 樹和繪圖上下文同時並存,直到頁面銷燬。

RenderObject 樹,基於 DOM 樹的一棵新樹,是佈局計算和渲染等機制的基礎設施。

DOM 節點建立新的 RenderObject 物件的時機:

  • DOM 樹的 Document 節點。
  • DOM 樹的可視節點,如html、body、div 等。非可視節點如meta、head、script 等不建立。
  • 為滿足 WebKit 處理,需要建立匿名 RenderObject 節點,它不對應於 DOM 樹的任何節點。如:匿名的 RenderBlock 節點。

DOM 樹的每個節點物件會遞迴檢查是否需要建立 RenderObject,並根據 DOM 節點型別建立 RenderObject 節點;動態加入的 DOM 元素,會相應的建立 RenderObject 節點。所有這些節點構成一棵 RenderObject 樹。

3. 渲染基礎:網頁層次和 RenderLayer 樹

在 HTML 頁面上,網頁分層展示。目的有兩個:1. 方便開發網頁、設定網頁的層次;2. 簡化 WebKit 渲染的邏輯。

在RenderObject 樹基礎上,WebKit 根據需要為其中的某些節點建立新的 RenderLayer 節點,並形成一棵 RenderLayer 樹。

RenderObject 節點建立新 RenderLayer 物件的時機:

  • DOM 樹的 Document 節點對應的 RenderView 節點。
  • DOM 樹的 Document 的子節點,即 HTML 節點對應的 RenderBlock 節點。
  • 顯式的指定 CSS 位置的 RenderObject 節點。
  • 有透明效果的 RenderObject 節點。
  • 節點有溢位 overflow、alpha 或反射等效果的 RenderObject 節點。
  • 使用Canvas 2D、3D (WebGL)技術的 RenderObject 節點。
  • Video 節點對應的 RenderObject 節點。

RenderLayer 節點的使用可以有效減少網頁結構的複雜程度,並在許多情況下能減少重新渲染的開銷。

4. 佈局計算及重繪時機

CSS 盒模型,是佈局計算的基礎;渲染引擎用來確定如何排版元素、及元素間的位置關係。

佈局計算,是針對 RenderObject 樹及其子樹的計算,是一種遞迴計算,其節點資訊需要先計算其子節點的位置、大小等資訊。RenderObject 物件會將計算結果儲存,等待渲染時機。

  • 每個元素會實現自己的 layout。
  • 頁面元素定義了寬高,則按自定義寬高確定元素大小。
  • 文字節點等內聯元素,需要結合字號大小、文字多少確定寬高。
  • 頁面元素確定的寬高超過了佈局容器包含塊提供的寬高,同時 overflow 為 visible 或 auto,WebKit 則提供滾動條保證可顯示所有內容。
  • 一般頁面元素的寬高是在佈局時通過計算得來。除非網頁定義了頁面元素的寬高。

重繪時機:只要樣式發生變化,就重新計算。

  • 首次開啟頁面,瀏覽器設定網頁的可視區域,並呼叫計算佈局的方法。可視區域改變時,網頁包含塊的大小也會改變,WebKit 需要重新計算佈局。
  • 網頁的動畫會觸釋出局計算。動畫可能改變樣式屬性。
  • JavaScript 通過 CSSOM(CSS 物件模型) 直接修改樣式,會觸發 WebKit 重新計算佈局。
  • 使用者互動,如滾動網頁。

前端優化建議,因佈局計算耗時間,一旦佈局發生變化,WebKit 就需要後面的重新繪製操作。SO,減少樣式的變動~減少重繪~利用 CSS3 新功能(如 CSS3 變形 translate、scale、rotate 等方法,過渡 transition 方法等)可有效提高網頁的渲染效率。

六、 網頁渲染過程:由 RenderLayer 樹到最終的影像

在上一個過程,網頁完成了 DOM 樹到 RenderLayer 樹的佈局計算和排版處理。接下來,由渲染引擎(一般是繪圖類工具)完成對 RenderLayer 樹的繪製,並最終形成影像,展示給使用者。

1. 繪圖上下文

繪圖上下文,所有的繪圖操作都是在該上下文中進行的。它是一個與平臺無關的抽象類,它將每個繪圖操作橋接到不同的繪圖具體實現類。

2D 繪圖上下文:

  • 提供基本繪圖單元的繪製介面及設定繪圖的樣式。
  • 繪圖介面包括:畫點、畫線、畫圖、畫多邊形、畫文字等。繪圖樣式包括顏色、線寬、字號、漸變等。
  • CPU 來完成 2D 操作。或用 3D 圖形介面( OpenGL )完成。

3D 繪圖上下文:支援 CSS3D、WebGL 等。

  • 使用 3D 圖形介面(OpenGL、Direct3D 等)

2. 渲染方式

軟體渲染:CPU。通常渲染的結果是一個點陣圖,繪製每一層時都使用該點陣圖,區別在於位置可能不同,每一層按從後到前的順序。沒必要為每層分配一個點陣圖,沒必要合成。

缺點:對 HTML5 新技術,

  • 能力不足,CSS3D、WebGL;
  • 效能不好,如視訊、Canvas 2D;
  • 使用率下降,特別是移動端。

優勢:對更新區域處理,軟體渲染可能只需要計算極小區域,硬體則需要繪製其中一層或多層,再合成。硬體代價大。

硬體加速渲染:GPU 必須有合成的步驟。分層繪製+合成。不過對於更新區域,如果只是在一個層,硬體可能會更快。

WebKit 的實現方式:

  • 使用合適的網頁分層技術、減少重新計算的佈局和繪圖。
  • 使用CSS 3D 變形和動畫技術。CSS 3D 變形技術,能讓瀏覽器僅使用合成器合成所有層就可以達到動畫效果。不需要佈局計算和重繪~

前端優化建議:

  • 減少重繪:因為重繪是要計算佈局、繪圖、合成三個階段。其中計算佈局和繪圖比較費時,合成要少

七、總結

至此,從輸入 URL 到頁面呈現,我們大致做了介紹。但這只是皮毛最上方的一點,更多瀏覽器核心的實質,值得我們下載一份原始碼,編譯解析深挖~ 相信在前端優化的路上,知其然,知其所以然~ 定會走得跟遠~~

八、參考資料

相關文章