深入了解JavaScript阻塞渲染
前言:
在中文社區(qū),這么多年一直流傳一個說法:JS線程負(fù)責(zé)執(zhí)行JS,GUI渲染線程負(fù)責(zé)渲染,這兩者是互斥的,所以JS執(zhí)行時會阻塞渲染。但隨著Dev Tools使用的增多,逐漸開始懷疑以上說法。本文會以實(shí)際案例來解釋為什么JS阻塞渲染。
到底幾個線程
在講解JS線程與GUI線程互斥的文章中,通常會列出渲染進(jìn)程包含的線程,比如:
GUI渲染線程JS引擎線程- 事件觸發(fā)線程
- 定時觸發(fā)器線程
HTTP請求線程
但是,我們以百度的搜索頁舉例,打開Performance面板開啟錄制:

上圖錄制結(jié)果中:
Chrome_ChildIOThread對應(yīng)IO線程的任務(wù)記錄,用戶輸入、網(wǎng)絡(luò)、設(shè)備相關(guān)事件都與他相關(guān)Raster記錄光柵化線程池任務(wù)、GPU記錄GPU合成位圖的任務(wù)、Compositor記錄合成線程的任務(wù)執(zhí)行,以上三者都與瀏覽器渲染相關(guān)Main記錄渲染進(jìn)程的主線程中的任務(wù)
從這個角度看,瀏覽器實(shí)際的線程情況與那些GUI線程相關(guān)的文章描述的并不相同。
主線程的任務(wù)
接下來,讓我們進(jìn)入Main。紅線框內(nèi)長短不一的灰色塊,就是主線程中執(zhí)行的任務(wù)。

注意看紅框內(nèi)的綠色塊FP,代表First Paint(首次繪制):

那么在首次繪制前都要執(zhí)行什么任務(wù)呢?可以看到主要有3個Task(任務(wù)):

第一個任務(wù)是請求HTML數(shù)據(jù):

Parse HTML
當(dāng)請求回HTML字節(jié)流后,開始第二個任務(wù),將HTML字節(jié)流解析為DOM,這個任務(wù)的名字就是圖中的藍(lán)色塊Parse HTML:

注意其中有些執(zhí)行時長不一的Evaluate Script,這些是解析DOM樹過程中遇到的JS代碼。
從DOM樹中可以看到這些阻塞DOM樹生成的JS腳本:

他們的存在顯著拉長了Parse HTML的用時。
Recaculate Style
解析完DOM樹(藍(lán)色Parse HTML)后,下一個任務(wù)是紫色Recaculate Style:

他負(fù)責(zé)將HTML中的CSS樣式(外聯(lián)、內(nèi)聯(lián))輸出為styleSheets,styleSheets有兩個作用:
- 可以與
DOM樹結(jié)合為頁面帶來樣式 JS可以操作styleSheets改變頁面樣式
我們可以從控制臺打印document.styleSheets直觀感受他的存在:

Layout
有了DOM樹與styleSheets,接下來需要為視圖中可見部分生成一棵樹(比如display: none部分就不需要在這棵樹中顯示)。
這個任務(wù)是紫色Layout:

Update Layer Tree
用戶看到的頁面實(shí)際是由多層頁面重疊后的結(jié)果,開發(fā)者可以用很多手段(比如z-index)改變某部分的層級。
比如滾動條就會形成自己獨(dú)立的層級:

既然是多層結(jié)構(gòu),那么就需要更新每層的信息,這個任務(wù)是紫色的Update Layer Tree:

Paint
我們可以發(fā)現(xiàn),在FP之前,Update Layer Tree之后只剩下Paint這一任務(wù)了:

從字面意義講,這就是繪制么?并不是。
Paint的任務(wù)是整理每一層頁面的繪制信息,構(gòu)成繪制列表,這些數(shù)據(jù)會交給合成線程負(fù)責(zé)后續(xù)繪制操作。

可以發(fā)現(xiàn),具體的繪制操作是交由合成線程完成,他與JS所在線程(主線程)并不是互斥的。
JS為啥阻塞渲染
我們現(xiàn)在知道,JS執(zhí)行與Paint任務(wù)都發(fā)生在主線程。
渲染被阻塞的原因很明顯:因?yàn)?code>Paint任務(wù)沒有及時執(zhí)行,即繪制列表沒有及時提交給合成線程。
之所以沒有及時執(zhí)行,可能是因?yàn)?code>JS執(zhí)行時間過長,導(dǎo)致這一幀沒有時間執(zhí)行Paint。
比如,我們打開B站,記錄下主線程的任務(wù)。
可以看到,有個JS執(zhí)行時長達(dá)到231.88ms,超過了一幀的時間,在此期間主線程就沒時間執(zhí)行Paint了:

總結(jié)
JS之所以阻塞渲染,是因?yàn)?code>JS執(zhí)行與渲染相關(guān)任務(wù)都在爭奪主線程有限的資源。當(dāng)JS執(zhí)行時間過長,渲染相關(guān)任務(wù)就沒時間執(zhí)行了。
到此這篇關(guān)于深入了解JavaScript阻塞渲染的文章就介紹到這了,更多相關(guān)JS阻塞渲染 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
動態(tài)加載iframe時get請求傳遞中文參數(shù)亂碼解決方法
這篇文章主要介紹了動態(tài)加載iframe時get請求傳遞中文參數(shù)亂碼解決方法,需要的朋友可以參考下2014-05-05
JavaScript獲取URL中參數(shù)querystring的方法詳解
這篇文章先給大家介紹了JavaScript獲取URL中參數(shù)querystring的方法,而后有詳解介紹了Location對象的屬性和,Location對象的方法,對大家的理解很有幫助,有需要的朋友們可以參考借鑒,下面來一起看看吧。2016-10-10
JavaScript實(shí)現(xiàn)梯形乘法表的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)梯形乘法表的方法,涉及基本javascript結(jié)合表格操作的技巧,需要的朋友可以參考下2015-04-04
bootstrap下拉列表與輸入框組結(jié)合的樣式調(diào)整
輸入框組默認(rèn)是div.input-group。接下來通過本文給大家介紹bootstrap下拉列表與輸入框組結(jié)合的樣式調(diào)整,感興趣的朋友一起看看吧2016-10-10

