大家好,我卡頌。
在中文社群,這麼多年一直流傳一個說法:
JS
執行緒負責執行JS
,GUI
渲染執行緒負責渲染,這兩者是互斥的,所以JS
執行時會阻塞渲染。
但隨著Dev Tools
使用的增多,逐漸開始懷疑以上說法。本文會以實際案例來解釋為什麼JS
阻塞渲染。
歡迎加入人類高質量前端框架群,帶飛
到底幾個執行緒
在講解JS
執行緒與GUI
執行緒互斥的文章中,通常會列出渲染程式包含的執行緒,比如:
GUI
渲染執行緒JS
引擎執行緒- 事件觸發執行緒
- 定時觸發器執行緒
HTTP
請求執行緒
等
但是,我們以百度的搜尋頁舉例,開啟Performance
皮膚開啟錄製:
上圖錄制結果中:
Chrome_ChildIOThread
對應IO
執行緒的任務記錄,使用者輸入、網路、裝置相關事件都與他相關Raster
記錄光柵化執行緒池任務、GPU
記錄GPU
合成點陣圖的任務、Compositor
記錄合成執行緒的任務執行,以上三者都與瀏覽器渲染相關Main
記錄渲染程式的主執行緒中的任務
從這個角度看,瀏覽器實際的執行緒情況與那些GUI
執行緒相關的文章描述的並不相同。
主執行緒的任務
接下來,讓我們進入Main
。紅線框內長短不一的灰色塊,就是主執行緒中執行的任務。
注意看紅框內的綠色塊FP
,代表First Paint
(首次繪製):
那麼在首次繪製前都要執行什麼任務呢?可以看到主要有3個Task
(任務):
第一個任務是請求HTML
資料:
Parse HTML
當請求回HTML
位元組流後,開始第二個任務,將HTML
位元組流解析為DOM
,這個任務的名字就是圖中的藍色塊Parse HTML
:
注意其中有些執行時長不一的Evaluate Script
,這些是解析DOM
樹過程中遇到的JS
程式碼。
從DOM
樹中可以看到這些阻塞DOM
樹生成的JS
指令碼:
他們的存在顯著拉長了Parse HTML
的用時。
Recaculate Style
解析完DOM
樹(藍色Parse HTML
)後,下一個任務是紫色Recaculate Style
:
他負責將HTML
中的CSS
樣式(外聯、內聯)輸出為styleSheets
,styleSheets
有兩個作用:
- 可以與
DOM
樹結合為頁面帶來樣式 JS
可以操作styleSheets
改變頁面樣式
我們可以從控制檯列印document.styleSheets
直觀感受他的存在:
Layout
有了DOM
樹與styleSheets
,接下來需要為檢視中可見部分生成一棵樹(比如display: none
部分就不需要在這棵樹中顯示)。
這個任務是紫色Layout
:
Update Layer Tree
使用者看到的頁面實際是由多層頁面重疊後的結果,開發者可以用很多手段(比如z-index
)改變某部分的層級。
比如滾動條就會形成自己獨立的層級:
既然是多層結構,那麼就需要更新每層的資訊,這個任務是紫色的Update Layer Tree
:
Paint
我們可以發現,在FP
之前,Update Layer Tree
之後只剩下Paint
這一任務了:
從字面意義講,這就是繪製麼?並不是。
Paint
的任務是整理每一層頁面的繪製資訊,構成繪製列表,這些資料會交給合成執行緒負責後續繪製操作。
可以發現,具體的繪製操作是交由合成執行緒完成,他與JS
所線上程(主執行緒)並不是互斥的。
JS為啥阻塞渲染
我們現在知道,JS
執行與Paint
任務都發生在主執行緒。
渲染被阻塞的原因很明顯:因為Paint
任務沒有及時執行,即繪製列表沒有及時提交給合成執行緒。
之所以沒有及時執行,可能是因為JS
執行時間過長,導致這一幀沒有時間執行Paint
。
比如,我們開啟B站,記錄下主執行緒的任務。
可以看到,有個JS
執行時長達到231.88ms,超過了一幀的時間,在此期間主執行緒就沒時間執行Paint
了:
總結
JS
之所以阻塞渲染,是因為JS
執行與渲染相關任務都在爭奪主執行緒有限的資源。
當JS
執行時間過長,渲染相關任務就沒時間執行了。