目錄
Document Object Model (DOM)
CSS Object Model (CSSOM)
Render Tree
Rendering Sequence
Layout operation
Paint operation
Compositing operation
界面渲染順序圖
Browser engines
Rendering Process in browsers
Parsing and External Resources
Parser-Blocking Scripts
Render-Blocking CSS
Document’s DOMContentLoaded Event
Window’s load event
有時候我們在使用某個網站的時候會出現影響用戶體驗的共性問題,例如:網站加載速度過慢、一直在等待檔案的加載、加載出來了界面卻沒有樣式等,為了避免開發人員開發這種網站,我們需要深入理解瀏覽器渲染界面的生命周期,
Document Object Model (DOM)
首先我們需要理解什么是DOM,瀏覽器向服務器發送請求獲取HTML資料,服務器以二進制位元組流的形式向瀏覽器回傳HTML文本,這個response的header中有這樣的attribute:Content-Type:text/html;charset=UTF-8,text/html是一種MIME Type,它告知瀏覽器這種MIME Type是HTML Document,charset=UTF-8告知瀏覽器該MIME Type檔案需要使用UTF-8的編碼方式解碼,根據這些資訊,瀏覽器就能將二進制位元組流轉化為我們看到的HTML Document,

如果丟失response header中的text/html,瀏覽器就不能理解怎樣處理該MIME Type,這個時候二進制位元組流將會被渲染普通文本格式,但是如果一切正常,經過瀏覽器對該MIMET Type檔案的轉化,典型的HTML Document最終看起來會是這樣:
<!DOCTYPE HTML>
<html>
<head>
<title>Rendering Test</title>
<!-- stylesheet -->
<link rel="stylesheet" href="./style.css"/>
</head>
<body>
<div class="container">
<h1>Hello World!</h1>
<p>This is a sample paragraph.</p>
</div>
<!-- script -->
<script src="./main.js"></script>
</body>
</html>
在上面的代碼段中,該網頁依賴于為網頁提供樣式的style.css和為網頁提供操作的main.js,在style.css添加一些炫酷的樣式,我們的網頁看起來如下圖所示:

但是問題仍然存在啊,瀏覽器是怎樣將平平無奇的只包含文本的HTML檔案渲染成如此炫酷的界面呢?為了解決這個問題我們需要從DOM、CSSOM、Render Tree入手,
無論何時瀏覽器決議HTML代碼,遇到HTML、body、div等元素,他都會創建與之對應的JavaScript Node物件,最終所有的元素都會被轉化為JavaScript物件,由于每個HTML元素都有不同的屬性,因此將通過不同的類(建構式)創建Node物件,例如:div對應的Node物件HTMLDivElement繼承自Node類,我們可以使用chrome的DevTools來做如下測驗:

瀏覽器為每個元素創建完物件之后,它將會為這些Node物件創建一個樹形結構,由于在HTML檔案中元素之間互相嵌套,所以瀏覽器需要復制檔案中的元素但是使用之前創建的Node物件來創建樹形結構,這個步驟將有助于瀏覽器在整個生命周期內有效地呈現和管理網頁,

這就是DOM Tree,其結構如上圖所示,
DOM Tree最頂端的元素是html,其分支是元素在檔案中出現和嵌套來顯示的,不論什么時候決議到HTML元素,瀏覽器都會創建與之對應的Node物件,
DOM節點并不一定總是HTML元素, 當瀏覽器創建DOM樹時,它還將諸如注釋,屬性,文本之類的內容另存為樹中的單獨節點, 但為簡單起見,我們僅考慮HTML元素(又稱為DOM元素)的DOM節點,想要了解DOM節點型別可以參考這里
DOM Tree通過實體物件的七個屬性描述節點之間的關系,構成層次結構
1) ownerDocument屬性: 該屬性指向整個節點樹中的檔案節點
2) parentNode屬性: 該屬性指向節點樹中該節點的父節點
3) previousSibling屬性: 該屬性指向節點樹中該節點的左兄弟節點
4) nextSibling屬性: 該屬性指向節點樹中該節點的右兄弟節點
5) childNodes屬性: 該屬性指向節點樹中該節點的子節點NodeList集合
6) firstChild屬性: 該屬性指向節點樹中該節點的子節點NodeList集合中的第一個位元組點
7) lastChild屬性: 該屬性指向節點樹中該節點的子節點NodeList集合中的最后一個位元組點
可以通過Chrome DevTools來觀察節點之間的繼承關系:

JavaScript不知道什么是DOM,DOM不是JavaScript規范的一部分,DOM是瀏覽器提供的高級Web API,用于高效地呈現網頁并將其公開顯示給開發人員,以便開發者動態操縱DOM元素,
使用DOM API開發人員可以對HTML元素進行增刪改查的操作,可以在記憶體中創建或者拷貝HTML元素,在不渲染DOM Tree的情況下操作HTML元素,這使開發人員能夠構建具有豐富用戶體驗的高度動態的網頁,
CSS Object Model (CSSOM)
當我們設計網站的時候,我們需要將其設計地盡善盡美,為了達到這個目標我們在HTML元素上提供一些樣式,在本頁面中我們使用的是Cascading Style Sheets(級聯樣式),使用CSS選擇器我們能夠定向操縱目標元素的樣式,
外部CSS檔案、通過<style>內嵌CSS樣式、在HTML元素上使用style屬性、使用JavaScript 應用在HTML元素上的方法是不同的,但是最終瀏覽器繁瑣地將CSS樣式應用在DOM元素上,
在構建好DOM Tree之后,瀏覽器會加載所有的CSS樣式(外部CSS檔案,<style>內嵌樣式,行內style屬性,用戶代理樣式等)來構建一個CSSOM,CSSOM是一個樹形結構的CSS物件模型,
CSSOM Tree上的每一個節點都會保存CSS樣式資訊,最侄訓被應用DOM Tree的目標元素上,CSSOM不包含無法在螢屏上顯示的<meta>、<script>等DOM元素,
瀏覽器本身會具有它自己的樣式檔案,定義我們沒有自定義CSS樣式的時候需要顯示的樣式,這被稱為用戶代理樣式,瀏覽器在計算樣式的時候會讓用戶自定義的樣式覆寫用戶代理樣式,
根據W3C CSS的標準,即使用戶和瀏覽器沒有定義該CSS屬性(例如:display),該屬性也會有默認值,在選擇CSS屬性的默認值時,如果某個屬性符合W3C檔案中提到的繼承條件,則將使用一些繼承規則,
例如,如果HTML元素缺少color和font-size這些屬性,則DOM元素會繼承父級的值, 因此,您可以想象在HTML元素及其所有子元素都擁有這些屬性, 這就是所謂的級聯樣式,這也是CSS是級聯樣式表的縮寫的原因, 這也是為什么瀏覽器構造CSSOM(一種類似樹的結構以根據CSS級聯規則計算樣式)的原因,
通過Chrome DevTools,從左側面板中選擇任何HTML元素,然后在右側面板中單擊“計算”選項卡可以觀察該元素的樣式,
為上面的HTML檔案添加如下樣式:
html {
padding: 0;
margin: 0;
}
body {
font-size: 14px;
}
.container {
width: 300px;
height: 200px;
color: black;
}
.container > h1 {
color: gray;
}
.container > p {
font-size: 12px;
display: none;
}
最侄訓構建如下CSSOM Tree:

Render Tree
Render Tree也是通過將DOM和CSSOM樹組合在一起而構建的樹狀結構, 瀏覽器必須計算每個可見元素的布局并將其繪制在螢屏上,因為該瀏覽器需要使用此Render Tree, 未構建Render Tree的情況下螢屏上不會顯示任何內容,這就是我們同時需要DOM和CSSOM樹的原因,
由于“渲染樹”是在螢屏上顯示的內容的底層表示,因此它不會包含不占據像素矩陣中任何區域的節點, 例如,display:none; 該元素的尺寸為0px X 0px,因此該元素不會出現在“渲染樹”中,

從上圖可以看到,Render-Tree結合了DOM和CSSOM進而生成樹狀結構,其中僅包含將在螢屏上列印的元素,
在CSSOM中,位于div內的p元素屬性為display:none,因此它及其子級不會出現在“渲染樹”中,因為它在螢屏上不占空間, 但是,如果元素的屬性為visibility:hidden或opacity:0,它們將占據螢屏上的空間,因此它們會出現在“渲染樹”中,
與DOM API允許訪問由瀏覽器構造的DOM Tree中的DOM元素不同,CSSOM對用戶隱藏, 但是,由于瀏覽器將DOM和CSSOM結合在一起形成了“Render Tree”,因此瀏覽器通過在DOM元素本身上提供高級API來公開DOM元素的CSSOM節點, 這使開發人員可以訪問或更改CSSOM節點的CSS屬性,
想要查看怎樣通過JavaScript對CSS進行操作,可以參考:CSS Tricks Article&CSS Typed Object
Rendering Sequence
現在,我們對DOM,CSSOM和Render Tree有了很好的了解,讓我們一起來了解瀏覽器如何使用它們來呈現網頁, 對這個程序的簡單了解對于任何Web開發人員都是至關重要的,因為它將幫助讓我們設計的網站獲得良好的用戶體驗和性能,
加載網頁后,瀏覽器將首先讀取HTML文本并從中構造DOM樹, 然后,它處理CSS(無論是嵌入式CSS,嵌入式CSS還是外部CSS),并從中構造CSSOM樹,
構造完這些樹后,便會從中構造出渲染樹, 一旦構建了Render Tree,瀏覽器便開始在螢屏上列印單個元素,
Layout operation
首先,瀏覽器創建每個單獨的“渲染樹”節點的布局, 布局包括每個節點的大小(以像素為單位)以及它將在螢屏上列印的位置, 由于瀏覽器正在計算每個節點的布局資訊,因此此程序稱為布局(layout),
此程序也稱為重排(reflow)或瀏覽器重排(browser reflow),并且在滾動,調整視窗大小或操作DOM元素時也可能發生, 這是可以觸發元素的布局/重排的事件串列,
我們應該避免網頁經歷多次布局操作,因為這是一項繁雜的操作, 這是保羅·劉易斯(Paul Lewis)發表的一篇文章,他談到了如何避免復雜而昂貴的布局操作也就是布局顛簸(layout thrashing),
Paint operation
到目前為止,我們具有了需要在螢屏上列印的幾何分布矩陣, 由于“渲染樹”中的元素(或子樹)可以彼此重疊,并且它們可以具有CSS屬性,這些屬性使它們經常更改外觀,位置或形狀(例如影片),因此瀏覽器會為其創建一個圖層(layer),
創建圖層可幫助瀏覽器在網頁的整個生命周期中有效執行繪畫操作,例如在滾動或調整瀏覽器視窗大小時,圖層還可以幫助瀏覽器正確地按照開發人員的意愿按順序(沿z軸)繪制元素,
現在我們有了圖層,我們可以將它們組合起來并在螢屏上繪制它們,但是瀏覽器并不會一次繪制所有圖層,會分別繪制每個圖層,
在每一層內部,瀏覽器會填充元素的任何可見屬性(例如邊框,背景色,陰影,文本等)的各個像素,此程序也稱為光柵化(raster),為了提高性能,瀏覽器可以使用不同的執行緒來執行光柵化,
Photoshop中的圖層可以用來類比瀏覽器中的呈現網頁的圖層,您可以通過Chrome DevTools可視化網頁上的不同圖層,打開DevTools,然后從more tools選項中選擇“Layers”,您也可以從該面板中可視化圖層邊框,

柵格化(raster)通常在CPU中完成,這樣的速度緩慢且CPU資源本就稀缺,現在我們有了新的技術可以在GPU中進行柵格化進而增強性能,這篇文章詳細介紹了該主題,關于layer同時可以閱讀這一篇文章來加強理解,
Compositing operation
到目前為止,我們還沒有在螢屏上繪制單個像素, 我們所擁有的是不同的層(bitmap images),應該以特定的順序在螢屏上繪制它們, 在合成操作中,這些層被發送到GPU,最后將其繪制在螢屏上,
發送整個圖層以進行繪制效率很低,所以每次進行reflow(layout)或repaint時都必須進行此操作, 因此,將一層分解為不同的塊(tiles),然后將其繪制在螢屏上, 您還可以在Chrome DevTool 的 Rendering面板中可視化這些塊(tiles),
界面渲染順序圖

這一系列事件也被稱為critical rendering path,
Browser engines
創建DOM Tree,CSSOM Tree和處理渲染邏輯的作業是使用瀏覽器引擎(也稱為渲染引擎或布局引擎)完成的, 該瀏覽器引擎包含所有將網頁從HTML代碼渲染為螢屏上的實際像素所需要的元素和邏輯,
如果你聽到有人在談論WebKit,那么他們在談論瀏覽器引擎, WebKit被Apple的Safari瀏覽器使用,并且是Google Chrome瀏覽器的默認渲染引擎,
Rendering Process in browsers
HTML,CSS或JavaScript,這些語言是由某個物體或某個組織標準化的, 但是,瀏覽器如何統籌管理它們并且在螢屏上呈現出來沒有相關的標準, Google Chrome瀏覽器的引擎功能可能與Safari瀏覽器的引擎功能不同,
因此,很難預測它們在特定瀏覽器中的渲染順序及其背后的機制, 但是,HTML5規范已經做出一些努力來標準化渲染程序在理論上應該如何作業,但是瀏覽器如何遵循此標準完全取決于各家廠商,
盡管存在這些不一致,但仍有一些通用原則被所有瀏覽器遵循,讓我們一起來了解瀏覽器在螢屏上呈現內容的常用方法以及此程序的生命周期事件,為了理解此程序,我準備了一個小專案來測驗不同的渲染方案(下面的鏈接),
course-one/browser-rendering-test
Parsing and External Resources
決議的程序就是瀏覽器引擎不斷讀取HTML Document并構建DOM Tree的程序,因此這個程序也被稱為DOM parsing,處理者也被稱為決議器(DOM parser),
許多瀏覽器提供DOM parser API構建DOM Tree,DOMParser類的一個實體表示一個DOM決議器,使用parseFromString原型方法,我們可以將原始HTML文本決議為一個DOM Tree,

瀏覽器對網頁發出請求,服務器做出回應,其中一些檔案的的頭部會被設定為Content-Type:text/ HTML,只要在該文本中加載出來字符(某一時刻該文本可能只加載出來了幾個字符或者幾行字符等等),瀏覽器就可以開始決議HTML,因此,瀏覽器可以增量地構建DOM樹,一次一個節點,從上到下決議HTML,而不是在中間的任何位置,因為HTML表示一個嵌套的樹狀結構,

在上面的例子中,我們通過Chrome DevTools限制網速并訪問incremental.html,瀏覽器會花費大量時間去加載檔案,然后它將會從文本的第一個位元組開始構建DOM Tree并print到界面上,

你可以觀察Chrome Devtools中的性能(performance) 面板,你可以看到在Timing面板中發生的一些事件,我們通常稱這些事件為“性能衡量標準”(performance metrics),當這些事件盡可能地靠近彼此并且發生得越早,用戶體驗就越好,
FP是First Paint的首字母縮寫,意思是瀏覽器開始在螢屏上列印東西的時間(可以簡單抽象想象為列印body的背景色的第一個像素),
FCP是First Contentful Paint的首字母縮寫,意思是瀏覽器呈現文本或影像等內容的第一個像素的時間,LCP是“Largest Contentful Paint的縮寫,意思是瀏覽器渲染大文本或影像的時間,
你可能聽說過FMP(first meaningful paint),它也是一個類似于LCP的度量標準,但它已經從Chrome中洗掉,取而代之的是LCP,
L表示由瀏覽器在視窗物件上發出的onload事件,類似地,DCL表示在檔案物件上發出的DOMContentLoaded事件,
當瀏覽器遇到外部資源,如一個JavaScript腳本檔案< script src = " url " > < /script>,一個CSS樣式表檔案< link rel = "stylesheet" href = " url " / >,一個img檔案< img src = " url " / >或任何其他外部資源,瀏覽器會在后臺下載檔案,
其中讀者需要了解的也是最重要的是DOM決議通常發生在main thread上, 因此,如果主JavaScript執行執行緒繁忙,DOM會直到main thread空閑才開始決議, 您可能會問為什么這是如此重要? 因為腳本元素是parse-blocking的, 除腳本(.js)檔案請求外,其他外部檔案請求(例如影像,樣式表,pdf,視頻等)都不會阻止DOM構建/決議,
Parser-Blocking Scripts
parser-blocking script是會使HTML停止決議的腳本檔案/代碼, 當瀏覽器遇到腳本元素(如果它是嵌入式腳本)時,它將首先執行該腳本,然后繼續決議HTML以構造DOM Tree, 因此,所有嵌入式腳本都是parser-blocking的,
如果腳本元素是外部腳本檔案,瀏覽器將開始從主執行緒下載外部腳本檔案,但是它將停止執行主執行緒,直到完成下載, 這意味著在下載腳本檔案之前不能進行DOM決議,
一旦下載了腳本檔案,瀏覽器將首先在主執行緒上執行下載的腳本檔案,然后繼續進行DOM決議,如果瀏覽器再次在HTML中找到另一個腳本元素,它將執行相同的操作,為什么瀏覽器必須停止DOM決議來下載并執行JavaScript?
因為JavaScript可以在運行時(runtime)訪問DOM API,我們可以使用JavaScript訪問和操作DOM元素,這就是動態Web框架(dynamic web frameworks,例如React和Angular)的作業方式,但是,如果瀏覽器并行運行DOM決議和腳本執行,則DOM決議器執行緒和主執行緒之間可能存在競爭情況(race conditions,對共享資源的同時訪問會出現競爭情況),這就是為什么DOM決議必須在主執行緒上進行的原因,
但是,在大多數情況下,完全不必在后臺下載腳本檔案時停止DOM決議,因此,HTML5為我們提供了script標簽的async屬性,當DOM決議器遇到具有async屬性的外部腳本元素時,即使在后臺下載腳本檔案,也不會停止DOM決議程序,但是,一旦下載了檔案,決議將停止并執行腳本,
我們還為script元素提供了defer屬性,該屬性的作用類似于async屬性,但與async屬性不同的是,即使檔案已完全下載,該腳本也不會立刻執行,決議器決議完所有HTML之后,將執行所有具有defer屬性的腳本,這意味著DOM樹已完全構建,與異步腳本不同,所有延遲腳本都按照它們在HTML檔案(或DOM樹)中出現的順序執行,
所有常規腳本(嵌入式或外部)在停止DOM的構建時都被決議器阻止,在下載完成之前,它們不會阻止決議器執行,下載完成后,它將立即阻止決議器執行,但是,所有延遲腳本(deferred scripts)都是不會阻止決議器的執行,它們在完全構建DOM樹之后執行,

在上面的示例中,parser-blocking.html檔案在30個元素之后開始加載腳本,這就是為什么瀏覽器首先會顯示30個元素,停止DOM決議并開始加載腳本檔案, 第二個腳本檔案沒有延遲,因為它具有defer屬性,它將在DOM樹完全構建后執行,

如果我們查看“性能”面板,則FP和FCP會盡可能提前顯示,因為只要有一些HTML內容可用,瀏覽器就會開始構建DOM樹,在螢屏上盡可能呈現一些像素,
LCP在5秒鐘后發生,因為parser-blocking script將DOM parsing阻止了5秒鐘(其下載時間占用5秒鐘),并且當DOM決議器被阻止時,螢屏上僅呈現了30個文本元素,這不足命名為 largest paint(根據Google Chrome標準), 一旦下載并執行了腳本,便會重新進行DOM決議,并在螢屏上呈現大量內容,從而引發LCP事件,
parser blocking 經常和 render blocking 關聯起來,但是這兩者是不同的,因為DOM Tree沒有構建成功之前rendering是不會發生的,
某些瀏覽器可能包含speculative parsing,其中HTML parsing(而不是DOM Tree construction)被裝載到單獨的執行緒中,以便瀏覽器可以盡量讀取諸如link,script,img等元素,并下載這些資源,
如果你有三個腳本檔案,最好將這三個檔案的加載放在一起,決議第一個腳本的時候,DOM決議器無法讀取第二個腳本元素,因此在下載第一個腳本之前,瀏覽器將無法開始下載第二個腳本, 我們可以使用async標簽解決此問題,但這樣一來就不能保證異步腳本按順序執行,
之所以稱為推測性決議(speculative parsing),是因為瀏覽器會猜測將來可能會加載某些特定的資源,因此會在后臺將其加載, 但是,如果某些JavaScript處理DOM并使用外部資源操作該元素,則該策略將失敗,并且加載過的這些檔案將一無是處,
每個瀏覽器都有自己的策略,因此不能保證何時或是否會進行推測決議, 但是,我們可以要求瀏覽器使用<link rel =“ preload”>標簽提前加載一些資源,
Render-Blocking CSS
我們在實際應用程序中竟然發現CSS也可以阻止DOM決議????真的是這樣嗎????好吧,為了弄懂其中原理,我們需要了解渲染程序,
瀏覽器內部的瀏覽器引擎根據從服務器以文本檔案形式接收的HTML文本構造DOM樹,同樣,它從樣式檔案(例如,外部CSS檔案或HTML中的嵌入式CSS)構造CSSOM Tree,
DOM Tree和CSSOM Tree的構造都發生在主執行緒上,并且這些樹可以同時構造,最終他們會共同形成Render Tree,用于在螢屏上paint內容,Render Tree隨著DOM Tree的逐漸構建而逐漸構建,
正如我們所了解的那樣,DOM Tree的生成是incremental,這意味著在瀏覽器讀取HTML時,它將向DOM Tree中添加DOM元素,但是CSSOM Tree不是這種情況,與DOM Tree不同,CSSOM Tree的構建不是增量的,必須以特定的方式進行,
當瀏覽器找到<style>塊時,它將決議所有嵌入式CSS并使用新的CSS規則更新CSSOM Tree,之后,它將繼續以正常方式決議HTML,行內樣式也是如此,但是,當瀏覽器遇到外部樣式表檔案時,情況會截然不同,與外部腳本檔案不同,外部樣式表檔案不會阻止決議器執行,因此瀏覽器可以在后臺默默下載,并且DOM決議將繼續進行,
但是與HTML檔案(用于DOM構建)不同,瀏覽器不會一次只處理一個位元組的樣式表檔案內容,這是因為瀏覽器在讀取CSS檔案時無法逐步構建CSSOM Tree,原因是檔案末尾的CSS規則可能會覆寫檔案頂部寫的CSS規則,
因此,如果瀏覽器在決議樣式表內容時開始逐步構建CSSOM Tree,則由于相同的CSSOM節點將被更新,這會導致一顆CSSOM Tree的多次渲染,因為后面的樣式將覆寫前面的樣式,如果事實是這樣的話,我們在瀏覽器上加載網頁的時候將會看到元素的布局,顏色等樣式不斷變化,直到某一時刻才穩定下來,由于CSS樣式是級聯的,因此一項規則更改也會影響許多元素,
因此,瀏覽器不會增量(incrementally)處理外部CSS檔案,并且在處理樣式表中的所有CSS規則之后,會立即進行CSSOM Tree更新, CSSOM樹更新完成后,將更新“Render Tree”,然后將其呈現在螢屏上,
CSS也是一種阻止渲染的資源,瀏覽器發出獲取外部樣式表的請求后,將停止“Render Tree”構建,因此,關鍵渲染路徑(Critical Rendering Path ---- CRP)會被卡住,在螢屏上不會渲染任何內容,如下所示,但是,在后臺下載樣式表時,仍在進行DOM樹構建,

瀏覽器本來可以使用CSSOM Tree的“舊狀態”來生成“Render Tree”,因為決議HTML是逐步的,在螢屏上渲染也是逐步的,但這有很大的缺點,在這種情況下,一旦下載并決議了樣式表,并且更新了CSSOM,Render Tree將被更新并呈現在螢屏上,現在,使用“舊狀態”樣式的CSSOM重新使用“新狀態”繪制可能導致出現“無狀態”樣式內容(Flash of Unstyled Content----FOUC),這對于UX(用戶體驗)來說是非常不好的,
因此,瀏覽器將等待,直到樣式表被加載并決議,決議了樣式表并更新了CSSOM之后,將更新“Rneder Tree”,并且將解除關鍵渲染路徑(CRP)的阻塞,從而在螢屏上繪制“Rneder Tree”,由于這個原因,建議HTML檔案盡量在header加載所有外部樣式表,
讓我們想象一個場景,其中瀏覽器已經開始決議HTML,并且遇到一個外部樣式表檔案,它將開始在后臺下載檔案,阻止關鍵渲染路徑(CRP)并繼續進行DOM決議,但是隨后它遇到了一個腳本標簽,因此它將開始下載外部腳本檔案并阻止DOM決議,現在,瀏覽器處于空閑狀態,等待樣式表和腳本檔案完全下載,
但是如果外部腳本檔案已完全下載,而樣式表仍在后臺下載,瀏覽器應該執行腳本檔案嗎?這樣做有什么危害嗎?
CSSOM提供了一個高級JavaScript API與DOM元素的樣式進行互動,例如,您可以使用elem.style.backgroundColor屬性讀取或更新DOM元素的背景顏色,與elem元素關聯的樣式物件已經公開了CSSOM API,并且還有許多其他API也可以做到這一點(詳情請閱讀此css-tricks文章),
由于在后臺下載樣式表,因此JavaScript仍可以執行,因為主執行緒沒有被裝入的樣式表阻塞,如果我們的JavaScript程式(通過CSSOM API)訪問DOM元素的CSS屬性,我們將獲得適當的值(根據CSSOM的當前狀態),
但是一旦下載并決議了樣式表,CSSOM Tree將會更新,我們的JavaScript現在具有該元素的“舊狀態”樣式,因為新的CSSOM更新可能會更改該DOM元素的CSS屬性,因此,在下載樣式表時執行JavaScript是不安全的,
根據HTML5規范,瀏覽器可以下載腳本檔案,但是除非決議了所有以前的樣式表,否則它不會執行,當樣式表阻止腳本的執行時,它稱為腳本阻止樣式表(script-blocking stylesheet)或腳本阻止CSS(script-blocking CSS),

在上面的示例中,script-blocking.html包含一個鏈接標記(用于外部樣式表),后跟一個腳本標記(用于外部JavaScript), 在這里,腳本的下載速度非常快,沒有任何延遲,但是樣式表的下載需要6秒鐘, 因此,即使腳本已完全下載(如“Network”面板中所見),瀏覽器也不會立即執行該腳本, 僅在加載樣式表之后,我們才能看到腳本記錄的Hello World訊息,
像async或defer屬性使腳本元素成為non-parser-blocking document,也可以使用media屬性將外部樣式表標記為non-render-blocking, 使用media屬性,瀏覽器可以智能決定何時加載樣式表,
Document’s DOMContentLoaded Event
DOMContentLoaded(DCL)事件標記了瀏覽器何時從現有可用的HTML元素成功構建了完整的DOM Tree, 但是,觸發DCL事件涉及許多可變因素,
document.addEventListener( 'DOMContentLoaded', function(e) {
console.log( 'DOM is fully parsed!' );
} );
如果我們的HTML不包含任何腳本,則不會阻止DOM決議,并且DCL將會在瀏覽器能夠決議整個HTML檔案時迅速啟動,如果我們有parser-blocking腳本,則DCL必須等待,直到所有parser-blocking腳本都下載并執行,
將樣式表應用在圖片上時,情況變得有些復雜,即使您沒有外部腳本,DCL也會等到所有樣式表都加載完畢,由于DCL標志著整個DOM樹準備就緒的時間點,但是除非CSSOM也已完全構建,否則DOM Tree將無法安全訪問(進而獲取樣式資訊),因此,大多數瀏覽器都等到所有外部樣式表都加載并決議完畢再觸發DCL,
Script-blocking stylesheet顯然會延遲DCL,在這種情況下,由于腳本正在等待樣式表加載,因此不會構建DOM樹,
DCL是網站性能指標之一,我們應該將DCL發生時間優化地盡可能的小,最佳實踐之一是在可能的情況下對腳本元素使用defer和async標簽,以便在后臺下載腳本時瀏覽器可以執行其他操作,其次,我們應該優化the script-blocking and render-blocking stylesheets,
Window’s load event
JavaScript可以阻止DOM樹的生成,但是外部樣式表和檔案(例如影像,視頻等)卻并非如此,
DOMContentLoaded事件標記了完全構建DOM樹并且可以安全訪問的時間點,window.onload事件標記了外部樣式表和檔案、Web應用程式已完成下載的時間點,
window.addEventListener( 'load', function(e) {
console.log( 'Page is fully loaded!' );
} )

在上面的示例中,rendering.html檔案的頭部具有一個外部樣式表,下載該樣式表大約需要5秒鐘,該樣式表將阻止接下來任何會被呈現的內容(因為它阻止了CRP),因此FP和FCP在5秒鐘后發生
此后,我們有一個img元素,完全加載大約需要10秒鐘,因此,瀏覽器將繼續在后臺下載此檔案,并繼續進行DOM決議和渲染(因為外部影像資源既不會阻止決議器也不會阻止渲染),
接下來,我們有三個外部JavaScript檔案,分別需要3秒鐘,6秒鐘和9秒鐘進行下載,它們不是異步的,這意味著總加載時間應接近18秒,因為在執行前一個腳本之前,后續腳本不會開始下載,但是,查看DCL事件,我們的瀏覽器似乎已經使用推測性策略下載了腳本檔案,因此總加載時間接近9秒,
最后一個可能影響DCL的檔案是最后一個腳本檔案,其加載時間為9秒(因為樣式表已在5秒內下載完畢),因此DCL事件發生在9.1秒左右,
我們還擁有另一個外部資源,即影像檔案,它一直在后臺加載,完全下載(需要10秒)后,所以在10.2秒后會觸發視窗的加載事件,這表明網頁(應用程式)已完全加載,
本文的內容主要來自于:How the browser renders a web page?
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/226244.html
標籤:其他
上一篇:this.$set的使用
下一篇:觀察者模式的彈幕案例
