從 8 道面試題看瀏覽器渲染過程與性能優(yōu)化

前言

移動互聯(lián)網(wǎng)時代,用戶對于網(wǎng)頁的打開速度要求越來越高。百度用戶體驗(yàn)部研究表明,頁面放棄率和頁面的打開時間關(guān)系如下圖 所示。

根據(jù)百度用戶體驗(yàn)部的研究結(jié)果來看,普通用戶期望且能夠接受的頁面加載時間在 3 秒以內(nèi)。若頁面的加載時間過慢,用戶就會失去耐心而選擇離開。

首屏作為直面用戶的第一屏,其重要性不言而喻。優(yōu)化用戶體驗(yàn)更是我們前端開發(fā)非常需要 focus 的東西之一。

本文我們通過 8 道面試題來聊聊瀏覽器渲染過程與性能優(yōu)化。

我們首先帶著這 8 個問題,來了解瀏覽器渲染過程,后面會給出題解~
  1. 為什么 Javascript 要是單線程的 ?

  2. 為什么 JS 阻塞頁面加載 ?

  3. css 加載會造成阻塞嗎 ?

  4. DOMContentLoaded 與 load 的區(qū)別 ?

  5. 什么是 CRP,即關(guān)鍵渲染路徑(Critical Rendering Path)? 如何優(yōu)化 ?

  6. defer 和 async 的區(qū)別 ?

  7. 談?wù)劄g覽器的回流與重繪 ?

  8. 什么是渲染層合并 (Composite) ?

進(jìn)程 (process) 和線程 (thread)

進(jìn)程(process)和線程(thread)是操作系統(tǒng)的基本概念。

進(jìn)程是 CPU 資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位)。

線程是 CPU 調(diào)度的最小單位(是建立在進(jìn)程基礎(chǔ)上的一次程序運(yùn)行單位)。

現(xiàn)代操作系統(tǒng)都是可以同時運(yùn)行多個任務(wù)的,比如:用瀏覽器上網(wǎng)的同時還可以聽音樂。

對于操作系統(tǒng)來說,一個任務(wù)就是一個進(jìn)程,比如打開一個瀏覽器就是啟動了一個瀏覽器進(jìn)程,打開一個 Word 就啟動了一個 Word 進(jìn)程。

有些進(jìn)程同時不止做一件事,比如 Word,它同時可以進(jìn)行打字、拼寫檢查、打印等事情。在一個進(jìn)程內(nèi)部,要同時做多件事,就需要同時運(yùn)行多個“子任務(wù)”,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程

由于每個進(jìn)程至少要做一件事,所以一個進(jìn)程至少有一個線程。系統(tǒng)會給每個進(jìn)程分配獨(dú)立的內(nèi)存,因此進(jìn)程有它獨(dú)立的資源。同一進(jìn)程內(nèi)的各個線程之間共享該進(jìn)程的內(nèi)存空間(包括代碼段,數(shù)據(jù)集,堆等)。

借用一個生動的比喻來說,進(jìn)程就像是一個有邊界的生產(chǎn)廠間,而線程就像是廠間內(nèi)的一個個員工,可以自己做自己的事情,也可以相互配合做同一件事情。

當(dāng)我們啟動一個應(yīng)用,計(jì)算機(jī)會創(chuàng)建一個進(jìn)程,操作系統(tǒng)會為進(jìn)程分配一部分內(nèi)存,應(yīng)用的所有狀態(tài)都會保存在這塊內(nèi)存中。

應(yīng)用也許還會創(chuàng)建多個線程來輔助工作,這些線程可以共享這部分內(nèi)存中的數(shù)據(jù)。如果應(yīng)用關(guān)閉,進(jìn)程會被終結(jié),操作系統(tǒng)會釋放相關(guān)內(nèi)存。

瀏覽器的多進(jìn)程架構(gòu)

一個好的程序常常被劃分為幾個相互獨(dú)立又彼此配合的模塊,瀏覽器也是如此。

以 Chrome 為例,它由多個進(jìn)程組成,每個進(jìn)程都有自己核心的職責(zé),它們相互配合完成瀏覽器的整體功能,

每個進(jìn)程中又包含多個線程,一個進(jìn)程內(nèi)的多個線程也會協(xié)同工作,配合完成所在進(jìn)程的職責(zé)。

Chrome 采用多進(jìn)程架構(gòu),其頂層存在一個 Browser process 用以協(xié)調(diào)瀏覽器的其它進(jìn)程。

優(yōu)點(diǎn)

由于默認(rèn) 新開 一個 tab 頁面 新建 一個進(jìn)程,所以單個 tab 頁面崩潰不會影響到整個瀏覽器。

同樣,第三方插件崩潰也不會影響到整個瀏覽器。

多進(jìn)程可以充分利用現(xiàn)代 CPU 多核的優(yōu)勢。

方便使用沙盒模型隔離插件等進(jìn)程,提高瀏覽器的穩(wěn)定性。

缺點(diǎn)

系統(tǒng)為瀏覽器新開的進(jìn)程分配內(nèi)存、CPU 等資源,所以內(nèi)存和 CPU 的資源消耗也會更大。

不過 Chrome 在內(nèi)存釋放方面做的不錯,基本內(nèi)存都是能很快釋放掉給其他程序運(yùn)行的。

瀏覽器的主要進(jìn)程和職責(zé)

主進(jìn)程 Browser Process

負(fù)責(zé)瀏覽器界面的顯示與交互。各個頁面的管理,創(chuàng)建和銷毀其他進(jìn)程。網(wǎng)絡(luò)的資源管理、下載等。

第三方插件進(jìn)程 Plugin Process

每種類型的插件對應(yīng)一個進(jìn)程,僅當(dāng)使用該插件時才創(chuàng)建。

GPU 進(jìn)程 GPU Process

最多只有一個,用于 3D 繪制等

渲染進(jìn)程 Renderer Process

稱為瀏覽器渲染進(jìn)程或?yàn)g覽器內(nèi)核,內(nèi)部是多線程的。主要負(fù)責(zé)頁面渲染,腳本執(zhí)行,事件處理等。 (本文重點(diǎn)分析)

渲染進(jìn)程 (瀏覽器內(nèi)核)

瀏覽器的渲染進(jìn)程是多線程的,我們來看看它有哪些主要線程 :

1. GUI 渲染線程

  • 負(fù)責(zé)渲染瀏覽器界面,解析 HTML,CSS,構(gòu)建 DOM 樹和 RenderObject 樹,布局和繪制等。

  • 當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時,該線程就會執(zhí)行。

  • 注意,GUI 渲染線程與 JS 引擎線程是互斥的,當(dāng) JS 引擎執(zhí)行時 GUI 線程會被掛起(相當(dāng)于被凍結(jié)了),GUI 更新會被保存在一個隊(duì)列中等到 JS 引擎空閑時立即被執(zhí)行。

2. JS 引擎線程

  • Javascript 引擎,也稱為 JS 內(nèi)核,負(fù)責(zé)處理 Javascript 腳本程序。(例如 V8 引擎)

  • JS 引擎線程負(fù)責(zé)解析 Javascript 腳本,運(yùn)行代碼。

  • JS 引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來,然后加以處理,一個 Tab 頁(renderer 進(jìn)程)中無論什么時候都只有一個 JS 線程在運(yùn)行 JS 程序。

  • 注意,GUI 渲染線程與 JS 引擎線程是互斥的,所以如果 JS 執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞。

3. 事件觸發(fā)線程

  • 歸屬于瀏覽器而不是 JS 引擎,用來控制事件循環(huán)(可以理解,JS 引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助)

  • 當(dāng) JS 引擎執(zhí)行代碼塊如 setTimeOut 時(也可來自瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點(diǎn)擊、AJAX 異步請求等),會將對應(yīng)任務(wù)添加到事件線程中

  • 當(dāng)對應(yīng)的事件符合觸發(fā)條件被觸發(fā)時,該線程會把事件添加到待處理隊(duì)列的隊(duì)尾,等待 JS 引擎的處理

  • 注意,由于 JS 的單線程關(guān)系,所以這些待處理隊(duì)列中的事件都得排隊(duì)等待 JS 引擎處理(當(dāng) JS 引擎空閑時才會去執(zhí)行)

4. 定時觸發(fā)器線程

  • 傳說中的 setInterval 與 setTimeout 所在線程

  • 瀏覽器定時計(jì)數(shù)器并不是由 JavaScript 引擎計(jì)數(shù)的,(因?yàn)?JavaScript 引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計(jì)時的準(zhǔn)確)

  • 因此通過單獨(dú)線程來計(jì)時并觸發(fā)定時(計(jì)時完畢后,添加到事件隊(duì)列中,等待 JS 引擎空閑后執(zhí)行)

  • 注意,W3C 在 HTML 標(biāo)準(zhǔn)中規(guī)定,規(guī)定要求 setTimeout 中低于 4ms 的時間間隔算為 4ms。

5. 異步 http 請求線程

  • 在 XMLHttpRequest 在連接后是通過瀏覽器新開一個線程請求。

  • 將檢測到狀態(tài)變更時,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個回調(diào)再放入事件隊(duì)列中。再由 JavaScript 引擎執(zhí)行。

瀏覽器渲染流程

如果要講從輸入 url 到頁面加載發(fā)生了什么,那怕是沒完沒了了…這里我們只談?wù)劄g覽器渲染的流程。

  1. 解析 HTML 文件,構(gòu)建 DOM 樹,同時瀏覽器主進(jìn)程負(fù)責(zé)下載 CSS 文件

  2. CSS 文件下載完成,解析 CSS 文件成樹形的數(shù)據(jù)結(jié)構(gòu),然后結(jié)合 DOM 樹合并成 RenderObject 樹

  3. 布局 RenderObject 樹 (Layout/reflow),負(fù)責(zé) RenderObject 樹中的元素的尺寸,位置等計(jì)算

  4. 繪制 RenderObject 樹 (paint),繪制頁面的像素信息

  5. 瀏覽器主進(jìn)程將默認(rèn)的圖層和復(fù)合圖層交給 GPU 進(jìn)程,GPU 進(jìn)程再將各個圖層合成(composite),最后顯示出頁面

題解

1. 為什么 Javascript 要是單線程的 ?

這是因?yàn)?Javascript 這門腳本語言誕生的使命所致!JavaScript 為處理頁面中用戶的交互,以及操作 DOM 樹、CSS 樣式樹來給用戶呈現(xiàn)一份動態(tài)而豐富的交互體驗(yàn)和服務(wù)器邏輯的交互處理。

如果 JavaScript 是多線程的方式來操作這些 UI DOM,則可能出現(xiàn) UI 操作的沖突。

如果 Javascript 是多線程的話,在多線程的交互下,處于 UI 中的 DOM 節(jié)點(diǎn)就可能成為一個臨界資源,

假設(shè)存在兩個線程同時操作一個 DOM,一個負(fù)責(zé)修改一個負(fù)責(zé)刪除,那么這個時候就需要瀏覽器來裁決如何生效哪個線程的執(zhí)行結(jié)果。

當(dāng)然我們可以通過鎖來解決上面的問題。但為了避免因?yàn)橐肓随i而帶來更大的復(fù)雜性,Javascript 在最初就選擇了單線程執(zhí)行。

2. 為什么 JS 阻塞頁面加載 ?

由于 JavaScript 是可操縱 DOM 的,如果在修改這些元素屬性同時渲染界面(即 JavaScript 線程和 UI 線程同時運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。

因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置 GUI 渲染線程與 JavaScript 引擎為互斥的關(guān)系。

當(dāng) JavaScript 引擎執(zhí)行時 GUI 線程會被掛起,GUI 更新會被保存在一個隊(duì)列中等到引擎線程空閑時立即被執(zhí)行。

從上面我們可以推理出,由于 GUI 渲染線程與 JavaScript 執(zhí)行線程是互斥的關(guān)系,

當(dāng)瀏覽器在執(zhí)行 JavaScript 程序的時候,GUI 渲染線程會被保存在一個隊(duì)列中,直到 JS 程序執(zhí)行完成,才會接著執(zhí)行。

因此如果 JS 執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞的感覺。

3. css 加載會造成阻塞嗎 ?

由上面瀏覽器渲染流程我們可以看出 :

DOM 解析和 CSS 解析是兩個并行的進(jìn)程,所以 CSS 加載不會阻塞 DOM 的解析

然而,由于 Render Tree 是依賴于 DOM Tree 和 CSSOM Tree 的,

所以他必須等待到 CSSOM Tree 構(gòu)建完成,也就是 CSS 資源加載完成(或者 CSS 資源加載失敗)后,才能開始渲染。

因此,CSS 加載會阻塞 Dom 的渲染

由于 JavaScript 是可操縱 DOM 和 css 樣式 的,如果在修改這些元素屬性同時渲染界面(即 JavaScript 線程和 UI 線程同時運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。

因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置 GUI 渲染線程與 JavaScript 引擎為互斥的關(guān)系。

因此,樣式表會在后面的 js 執(zhí)行前先加載執(zhí)行完畢,所以css 會阻塞后面 js 的執(zhí)行

4. DOMContentLoaded 與 load 的區(qū)別 ?

  • 當(dāng) DOMContentLoaded 事件觸發(fā)時,僅當(dāng) DOM 解析完成后,不包括樣式表,圖片。我們前面提到 CSS 加載會阻塞 Dom 的渲染和后面 js 的執(zhí)行,js 會阻塞 Dom 解析,所以我們可以得到結(jié)論:
    當(dāng)文檔中沒有腳本時,瀏覽器解析完文檔便能觸發(fā) DOMContentLoaded 事件。如果文檔中包含腳本,則腳本會阻塞文檔的解析,而腳本需要等 CSSOM 構(gòu)建完成才能執(zhí)行。在任何情況下,DOMContentLoaded 的觸發(fā)不需要等待圖片等其他資源加載完成。

  • 當(dāng) onload 事件觸發(fā)時,頁面上所有的 DOM,樣式表,腳本,圖片等資源已經(jīng)加載完畢。

  • DOMContentLoaded -> load。

5. 什么是 CRP,即關(guān)鍵渲染路徑(Critical Rendering Path)? 如何優(yōu)化 ?

關(guān)鍵渲染路徑是瀏覽器將 HTML CSS JavaScript 轉(zhuǎn)換為在屏幕上呈現(xiàn)的像素內(nèi)容所經(jīng)歷的一系列步驟。也就是我們上面說的瀏覽器渲染流程。

為盡快完成首次渲染,我們需要最大限度減小以下三種可變因素:

  • 關(guān)鍵資源的數(shù)量: 可能阻止網(wǎng)頁首次渲染的資源。

  • 關(guān)鍵路徑長度: 獲取所有關(guān)鍵資源所需的往返次數(shù)或總時間。

  • 關(guān)鍵字節(jié): 實(shí)現(xiàn)網(wǎng)頁首次渲染所需的總字節(jié)數(shù),等同于所有關(guān)鍵資源傳送文件大小的總和。

1. 優(yōu)化 DOM

  • 刪除不必要的代碼和注釋包括空格,盡量做到最小化文件。

  • 可以利用 GZIP 壓縮文件。

  • 結(jié)合 HTTP 緩存文件。

2. 優(yōu)化 CSSOM

縮小、壓縮以及緩存同樣重要,對于 CSSOM 我們前面重點(diǎn)提過了它會阻止頁面呈現(xiàn),因此我們可以從這方面考慮去優(yōu)化。

  • 減少關(guān)鍵 CSS 元素?cái)?shù)量

  • 當(dāng)我們聲明樣式表時,請密切關(guān)注媒體查詢的類型,它們極大地影響了 CRP 的性能 。

3. 優(yōu)化 JavaScript

當(dāng)瀏覽器遇到 script 標(biāo)記時,會阻止解析器繼續(xù)操作,直到 CSSOM 構(gòu)建完畢,JavaScript 才會運(yùn)行并繼續(xù)完成 DOM 構(gòu)建過程。

  • async: 當(dāng)我們在 script 標(biāo)記添加 async 屬性以后,瀏覽器遇到這個 script 標(biāo)記時會繼續(xù)解析 DOM,同時腳本也不會被 CSSOM 阻止,即不會阻止 CRP。

  • defer: 與 async 的區(qū)別在于,腳本需要等到文檔解析后( DOMContentLoaded 事件前)執(zhí)行,而 async 允許腳本在文檔解析時位于后臺運(yùn)行(兩者下載的過程不會阻塞 DOM,但執(zhí)行會)。

  • 當(dāng)我們的腳本不會修改 DOM 或 CSSOM 時,推薦使用 async 。

  • 預(yù)加載 —— preload & prefetch 。

  • DNS 預(yù)解析 —— dns-prefetch 。

總結(jié)

  • 分析并用 關(guān)鍵資源數(shù) 關(guān)鍵字節(jié)數(shù) 關(guān)鍵路徑長度 來描述我們的 CRP 。

  • 最小化關(guān)鍵資源數(shù): 消除它們(內(nèi)聯(lián))、推遲它們的下載(defer)或者使它們異步解析(async)等等 。

  • 優(yōu)化關(guān)鍵字節(jié)數(shù)(縮小、壓縮)來減少下載時間 。

  • 優(yōu)化加載剩余關(guān)鍵資源的順序: 讓關(guān)鍵資源(CSS)盡早下載以減少 CRP 長度 。

6. defer 和 async 的區(qū)別 ?

當(dāng)瀏覽器碰到 script 腳本的時候 :

1. <script src="script.js">

沒有 defer 或 async,瀏覽器會立即加載并執(zhí)行指定的腳本,“立即”指的是在渲染該 script 標(biāo)簽之下的文檔元素之前,也就是說不等待后續(xù)載入的文檔元素,讀到就加載并執(zhí)行。

2. <script async src="script.js">

有 async,加載和渲染后續(xù)文檔元素的過程將和 script.js 的加載與執(zhí)行并行進(jìn)行(異步)。

3. <script defer src="myscript.js">

有 defer,加載后續(xù)文檔元素的過程將和 script.js 的加載并行進(jìn)行(異步),但是 script.js 的執(zhí)行要在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。

從實(shí)用角度來說,首先把所有腳本都丟到 </body> 之前是最佳實(shí)踐,因?yàn)閷τ谂f瀏覽器來說這是唯一的優(yōu)化選擇,此法可保證非腳本的其他一切元素能夠以最快的速度得到加載和解析。

接著,我們來看一張圖:

藍(lán)色線代表網(wǎng)絡(luò)讀取,紅色線代表執(zhí)行時間,這倆都是針對腳本的。綠色線代表 HTML 解析。

因此,我們可以得出結(jié)論:

  1. defer 和 async 在網(wǎng)絡(luò)讀取(下載)這塊兒是一樣的,都是異步的(相較于 HTML 解析)

  2. 它倆的差別在于腳本下載完之后何時執(zhí)行,顯然 defer 是最接近我們對于應(yīng)用腳本加載和執(zhí)行的要求的

  3. 關(guān)于 defer,此圖未盡之處在于它是按照加載順序執(zhí)行腳本的,這一點(diǎn)要善加利用

  4. async 則是一個亂序執(zhí)行的主,反正對它來說腳本的加載和執(zhí)行是緊緊挨著的,所以不管你聲明的順序如何,只要它加載完了就會立刻執(zhí)行

  5. 仔細(xì)想想,async 對于應(yīng)用腳本的用處不大,因?yàn)樗耆豢紤]依賴(哪怕是最低級的順序執(zhí)行),不過它對于那些可以不依賴任何腳本或不被任何腳本依賴的腳本來說卻是非常合適的

7. 談?wù)劄g覽器的回流與重繪

回流必將引起重繪,重繪不一定會引起回流。

回流(Reflow)

當(dāng) Render Tree 中部分或全部元素的尺寸、結(jié)構(gòu)、或某些屬性發(fā)生改變時,瀏覽器重新渲染部分或全部文檔的過程稱為回流。

會導(dǎo)致回流的操作:

頁面首次渲染

瀏覽器窗口大小發(fā)生改變

元素尺寸或位置發(fā)生改變元素內(nèi)容變化(文字?jǐn)?shù)量或圖片大小等等)

元素字體大小變化

添加或者刪除可見的 DOM 元素

激活 CSS 偽類(例如::hover)

查詢某些屬性或調(diào)用某些方法

一些常用且會導(dǎo)致回流的屬性和方法:

clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
scrollIntoView()、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()

重繪(Repaint)

當(dāng)頁面中元素樣式的改變并不影響它在文檔流中的位置時(例如:color、background-color、visibility 等),瀏覽器會將新樣式賦予給元素并重新繪制它,這個過程稱為重繪。

性能影響

回流比重繪的代價要更高。

有時即使僅僅回流一個單一的元素,它的父元素以及任何跟隨它的元素也會產(chǎn)生回流。現(xiàn)代瀏覽器會對頻繁的回流或重繪操作進(jìn)行優(yōu)化:瀏覽器會維護(hù)一個隊(duì)列,把所有引起回流和重繪的操作放入隊(duì)列中,如果隊(duì)列中的任務(wù)數(shù)量或者時間間隔達(dá)到一個閾值的,瀏覽器就會將隊(duì)列清空,進(jìn)行一次批處理,這樣可以把多次回流和重繪變成一次。

當(dāng)你訪問以下屬性或方法時,瀏覽器會立刻清空隊(duì)列:

clientWidth、clientHeight、clientTop、clientLeft
offsetWidth、offsetHeight、offsetTop、offsetLeft
scrollWidth、scrollHeight、scrollTop、scrollLeft
width、height
getComputedStyle()
getBoundingClientRect()

因?yàn)殛?duì)列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的信息與隊(duì)列中操作引發(fā)的改變無關(guān),瀏覽器也會強(qiáng)行清空隊(duì)列,確保你拿到的值是最精確的。

如何避免

CSS
  • 避免使用 table 布局。

  • 盡可能在 DOM 樹的最末端改變 class。

  • 避免設(shè)置多層內(nèi)聯(lián)樣式。

  • 將動畫效果應(yīng)用到 position 屬性為 absolute 或 fixed 的元素上。

  • 避免使用 CSS 表達(dá)式(例如:calc())。

Javascript
  • 避免頻繁操作樣式,最好一次性重寫 style 屬性,或者將樣式列表定義為 class 并一次性更改 class 屬性。

  • 避免頻繁操作 DOM,創(chuàng)建一個 documentFragment,在它上面應(yīng)用所有 DOM 操作,最后再把它添加到文檔中。

  • 也可以先為元素設(shè)置 display: none,操作結(jié)束后再把它顯示出來。因?yàn)樵?display 屬性為 none 的元素上進(jìn)行的 DOM 操作不會引發(fā)回流和重繪。

  • 避免頻繁讀取會引發(fā)回流/重繪的屬性,如果確實(shí)需要多次使用,就用一個變量緩存起來。

  • 對具有復(fù)雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及后續(xù)元素頻繁回流。

8. 什么是渲染層合并 (Composite) ?

渲染層合并,對于頁面中 DOM 元素的繪制(Paint)是在多個層上進(jìn)行的。

在每個層上完成繪制過程之后,瀏覽器會將繪制的位圖發(fā)送給 GPU 繪制到屏幕上,將所有層按照合理的順序合并成一個圖層,然后在屏幕上呈現(xiàn)。

對于有位置重疊的元素的頁面,這個過程尤其重要,因?yàn)橐坏﹫D層的合并順序出錯,將會導(dǎo)致元素顯示異常。

RenderLayers 渲染層,這是負(fù)責(zé)對應(yīng) DOM 子樹。

GraphicsLayers 圖形層,這是負(fù)責(zé)對應(yīng) RenderLayers 子樹。

RenderObjects 保持了樹結(jié)構(gòu),一個 RenderObjects 知道如何繪制一個 node 的內(nèi)容, 他通過向一個繪圖上下文(GraphicsContext)發(fā)出必要的繪制調(diào)用來繪制 nodes。

每個 GraphicsLayer 都有一個 GraphicsContext,GraphicsContext 會負(fù)責(zé)輸出該層的位圖,位圖是存儲在共享內(nèi)存中,作為紋理上傳到 GPU 中,最后由 GPU 將多個位圖進(jìn)行合成,然后 draw 到屏幕上,此時,我們的頁面也就展現(xiàn)到了屏幕上。

GraphicsContext 繪圖上下文的責(zé)任就是向屏幕進(jìn)行像素繪制(這個過程是先把像素級的數(shù)據(jù)寫入位圖中,然后再顯示到顯示器),在 chrome 里,繪圖上下文是包裹了的 Skia(chrome 自己的 2d 圖形繪制庫)

某些特殊的渲染層會被認(rèn)為是合成層(Compositing Layers),合成層擁有單獨(dú)的 GraphicsLayer,而其他不是合成層的渲染層,則和其第一個擁有 GraphicsLayer 父層公用一個。

合成層的優(yōu)點(diǎn)

一旦 renderLayer 提升為了合成層就會有自己的繪圖上下文,并且會開啟硬件加速,有利于性能提升。

  • 合成層的位圖,會交由 GPU 合成,比 CPU 處理要快 (提升到合成層后合成層的位圖會交 GPU 處理,但請注意,僅僅只是合成的處理(把繪圖上下文的位圖輸出進(jìn)行組合)需要用到 GPU,生成合成層的位圖處理(繪圖上下文的工作)是需要 CPU。)

  • 當(dāng)需要 repaint 時,只需要 repaint 本身,不會影響到其他的層 (當(dāng)需要 repaint 的時候可以只 repaint 本身,不影響其他層,但是 paint 之前還有 style, layout,那就意味著即使合成層只是 repaint 了自己,但 style 和 layout 本身就很占用時間。)

  • 對于 transform 和 opacity 效果,不會觸發(fā) layout 和 paint (僅僅是 transform 和 opacity 不會引發(fā) layout 和 paint,其他的屬性不確定。)

一般一個元素開啟硬件加速后會變成合成層,可以獨(dú)立于普通文檔流中,改動后可以避免整個頁面重繪,提升性能。

注意不能濫用 GPU 加速,一定要分析其實(shí)際性能表現(xiàn)。因?yàn)?GPU 加速創(chuàng)建渲染層是有代價的,每創(chuàng)建一個新的渲染層,就意味著新的內(nèi)存分配和更復(fù)雜的層的管理。并且在移動端 GPU 和 CPU 的帶寬有限制,創(chuàng)建的渲染層過多時,合成也會消耗跟多的時間,隨之而來的就是耗電更多,內(nèi)存占用更多。過多的渲染層來帶的開銷而對頁面渲染性能產(chǎn)生的影響,甚至遠(yuǎn)遠(yuǎn)超過了它在性能改善上帶來的好處。

我自己建了個群,歡迎JAVA開發(fā)的朋友加入QQ群:976203838 進(jìn)行技術(shù)討論,里面資深架構(gòu)師會分享一些整理好的BATJ面試題:有Spring,MyBatis,Netty源碼分析,高并發(fā)、高性能、分布式、微服務(wù)架構(gòu)的原理,JVM性能優(yōu)化、分布式架構(gòu)等這些成為架構(gòu)師必備的知識體系。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • ———-正文開始———- 最近發(fā)現(xiàn)有不少介紹JS單線程運(yùn)行機(jī)制的文章,但是發(fā)現(xiàn)很多都僅僅是介紹某一部分的知識,而且...
    流動碼文閱讀 2,223評論 7 32
  • 大家都知道萬維網(wǎng)的應(yīng)用層使用了HTTP協(xié)議,并且用瀏覽器作為入口訪問網(wǎng)絡(luò)上的資源。用戶在使用瀏覽器訪問一個網(wǎng)站時需...
    SylvanasSun閱讀 2,162評論 1 12
  • 瀏覽器相關(guān)理解 1 概念理解 1.1 進(jìn)程和線程 進(jìn)程> 進(jìn)程是一個工廠,工廠有它的獨(dú)立資源> 工廠之間相互獨(dú)立 ...
    Haiya_32ef閱讀 901評論 0 0
  • 背景(做前端永遠(yuǎn)也跨不過去了就是性能優(yōu)化) 公司目前有大量前后端耦合項(xiàng)目,瀏覽器的首屏加載速度非常的慢,在不段優(yōu)化...
    天一呀閱讀 1,168評論 0 4
  • 發(fā)送 & 接收信息 數(shù)據(jù)是以“數(shù)據(jù)包”的形式通過互聯(lián)網(wǎng)發(fā)送,而數(shù)據(jù)包以字節(jié)為單位。當(dāng)你編寫一些 HTML、CSS ...
    mongofeng閱讀 946評論 0 0