瀏覽器渲染原理
我們很容易將瀏覽器引擎看作是一個黑盒子,就像電視資料一樣,黑盒子會指出顯示的資料。那麼瀏覽器是怎麼把資源/資料轉換到我們所看到的網頁的呢
1.1 構建物件模型
HTML描述了網頁的結構。為了理解HTML,瀏覽器引擎首先必須將其轉換為DOM(文件物件模型)。瀏覽器引擎中擁有解析器,它用於將HTML中的資料轉換為DOM
瀏覽器逐個構建DOM,只要第一行程式碼進來,他就開始解析HTML,向樹結構新增節點
具體的構建過程:
- 轉換: 瀏覽器從磁碟或網路中讀取HTML的原始位元組,並根據檔案的指定編碼
- 標籤化: 瀏覽器將字串轉換成符合HTML標準的標籤
- 詞法分析: 標籤轉換成定義其屬性和規則的物件
- DOM構建: 在HTML中,巢狀定義了不同標籤之間的父子關係。在DOM中,物件被連結到捕獲這些關係的樹資料結構中。每個HTML標記都由一個DOM節點表示。
在網頁上的CSS樣式被對映到CSSOM(CSS物件模型)。他非常像DOM,但與DOM不同,他不能逐步構建,由於CSS規則可以相互覆蓋,因此瀏覽器引擎必須進行復雜計算才能確定CSS如何應用於DOM中。換個說法只有把CSS全部載入完才能構建CSSOM物件模型,但這樣會阻塞頁面渲染。
與處理HTML時一樣,我們需要將收到的CSS規則轉換成瀏覽器能夠理解和處理的東西。因此我們會重複HTML過程,不過是為了CSS而不是HTML
CSS位元組轉換成字元,接著轉換成令牌和節點,最後連結到CSSOM物件模型的樹結構內:
1.2 渲染樹構建,佈局,繪製
在構建完DOM樹和CSSOM樹,我們需要將將其合併後形成渲染樹
構建
為了構建渲染樹,瀏覽器大體上完成了下列工作
- 從DOM樹的根節點開始遍歷每個可見節點,某些節點不可見(例如指令碼標記,元標記等),因為他們不會體現在渲染輸出中,所以會被忽略。某些節點通過CSS隱藏,因此在渲染樹中也會被隱藏,例如
display: none
- 對於每個可見節點,為其找到適配的CSSOM規則並應用他們
- 構建可見節點.連同其內容和計算的樣式
注意: visibility: hidden
與display: none
是不一樣的。前者隱藏元素,但元素仍佔據著佈局空間(即將其渲染成一個空的盒子),而後者則將元素從渲染樹中完全移除,元素即不可見,也不是佈局的組成部分。
佈局
有了渲染樹後,我們就可以進入佈局階段,到目前為止,我們計算了那些節點應該是可見的以及他們的計算樣式,但我們尚未計算他們在裝置視口內的確切位置和大小,這就是佈局階段,也稱為重排
HTML採用基於流的佈局模型.這意味著大多數情況下只要依次遍歷就能計算出幾何資訊。處於流中靠後位置元素通常不會影響靠前位置元素的幾何特徵,因此佈局可以按從左至有,從上至下的順序遍歷文件。
佈局是一個遞迴的過程,他從根節點(html
元素),開始,然後遞迴遍歷部分或所有的框架層次結構,為每一個需要計算的節點,計算幾何資訊
繪製
在繪製階段,系統會遍歷渲染樹,將渲染樹的內容顯示在螢幕上。
動態變化
在發生變化時,會盡可能做出最小的響應。因此,元素的顏色改變後,只會對該元素進行重繪。元素的位置改變後,只會對該元素及其子元素(可能還有同級元素)進行佈局和重繪。新增DOM節點後,會對該節點進行佈局和重繪。一些重大變化(例如增大html
元素的字型)會導致快取無效,使得整個渲染樹都會進行重新佈局和重繪
1.3 JS的問題
在瀏覽器構建DOM時,如果遇到script
標籤,他必須立即執行,如果指令碼是外部指令碼,則必須先下載指令碼
在過去,為了執行指令碼,解析必須暫停。他只會在JavaScript引擎從指令碼執行程式碼後再次啟動。
為什麼解析必須停止?這是因為js可以改變DOM,js可以通過新增節點來改變DOM結構,也可以使用document.wrtie()
新增內容來使HTML其餘部分無效
JS也可以查詢關於DOM的內容,如果這種情況在DOM仍在構建時發生,他可能會返回意外的結果
1.4 阻塞渲染的CSS
JavaScript阻止解析,因為它可以修改文件,CSS無法修改文件,似乎沒有理由阻塞解析,但是,JavaScript還可以讀取和修改CSSOM的屬性。如果瀏覽器尚未完成CSSOM的下載和構建,而我們此時想執行指令碼,瀏覽器將延遲JS執行和DOM構建,直至其完成CSSOM的下載和構建
1.5 defer和async
當然我們可以通過設定defer
和async
屬性來非同步載入不太重要的指令碼
這兩個屬性都告訴瀏覽器,他可能會在後臺載入指令碼時繼續解析HTML,然後再在載入後執行指令碼,這樣指令碼下載不會阻止DOM構建和頁面呈現。使用者可以在所有指令碼完成載入之前看到頁面
兩者的區別就是他們將在那一刻執行指令碼,在這之前我們需要了解瀏覽器為其載入的每個網頁追蹤細粒度時間戳
domLoading
: 瀏覽器即將開始解析第一批收到的HTML文件位元組domInteractive
: 表示瀏覽器完成對所有HTML的解析並且DOM構建完成的時間點domContentLoaded
: 表示DOM準備就緒並且沒有樣式表阻止JavaScript執行的時間點domComplete
: 所有處理完成,並且網頁上的所有資源都已經下載完畢loadEvent
: 作為每個網頁載入的最後一步,瀏覽器會觸發onload
事件,以便觸發額外的應用邏輯
defer
的執行將在domInteractive
完成之後,domContentLoaded
之前開始,他保證指令碼將按照他們在HTML中出現的順序執行,並且不會阻塞解析器
async
指令碼在完成下載之後和視窗load
事件之前的某一個時間點執行,這意味著非同步指令碼可能不按他們在HTML中出現的順序執行,這意味著他們可能會阻止DOM構建
參考連結:
Building the DOM faster: speculative parsing, async, defer and preload