深入了解JavaScript阻塞渲染
前言:
在中文社區(qū),這么多年一直流傳一個(gè)說法:JS
線程負(fù)責(zé)執(zhí)行JS
,GUI
渲染線程負(fù)責(zé)渲染,這兩者是互斥的,所以JS
執(zhí)行時(shí)會(huì)阻塞渲染。但隨著Dev Tools
使用的增多,逐漸開始懷疑以上說法。本文會(huì)以實(shí)際案例來解釋為什么JS
阻塞渲染。
到底幾個(gè)線程
在講解JS
線程與GUI
線程互斥的文章中,通常會(huì)列出渲染進(jìn)程包含的線程,比如:
GUI
渲染線程JS
引擎線程- 事件觸發(fā)線程
- 定時(shí)觸發(fā)器線程
HTTP
請(qǐng)求線程
但是,我們以百度的搜索頁舉例,打開Performance
面板開啟錄制:
上圖錄制結(jié)果中:
Chrome_ChildIOThread
對(duì)應(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ù)
從這個(gè)角度看,瀏覽器實(shí)際的線程情況與那些GUI
線程相關(guān)的文章描述的并不相同。
主線程的任務(wù)
接下來,讓我們進(jìn)入Main
。紅線框內(nèi)長(zhǎng)短不一的灰色塊,就是主線程中執(zhí)行的任務(wù)。
注意看紅框內(nèi)的綠色塊FP
,代表First Paint
(首次繪制):
那么在首次繪制前都要執(zhí)行什么任務(wù)呢?可以看到主要有3個(gè)Task
(任務(wù)):
第一個(gè)任務(wù)是請(qǐng)求HTML
數(shù)據(jù):
Parse HTML
當(dāng)請(qǐng)求回HTML
字節(jié)流后,開始第二個(gè)任務(wù),將HTML
字節(jié)流解析為DOM
,這個(gè)任務(wù)的名字就是圖中的藍(lán)色塊Parse HTML
:
注意其中有些執(zhí)行時(shí)長(zhǎng)不一的Evaluate Script
,這些是解析DOM
樹過程中遇到的JS
代碼。
從DOM
樹中可以看到這些阻塞DOM
樹生成的JS
腳本:
他們的存在顯著拉長(zhǎng)了Parse HTML
的用時(shí)。
Recaculate Style
解析完DOM
樹(藍(lán)色Parse HTML
)后,下一個(gè)任務(wù)是紫色Recaculate Style
:
他負(fù)責(zé)將HTML
中的CSS
樣式(外聯(lián)、內(nèi)聯(lián))輸出為styleSheets
,styleSheets
有兩個(gè)作用:
- 可以與
DOM
樹結(jié)合為頁面帶來樣式 JS
可以操作styleSheets
改變頁面樣式
我們可以從控制臺(tái)打印document.styleSheets
直觀感受他的存在:
Layout
有了DOM
樹與styleSheets
,接下來需要為視圖中可見部分生成一棵樹(比如display: none
部分就不需要在這棵樹中顯示)。
這個(gè)任務(wù)是紫色Layout
:
Update Layer Tree
用戶看到的頁面實(shí)際是由多層頁面重疊后的結(jié)果,開發(fā)者可以用很多手段(比如z-index
)改變某部分的層級(jí)。
比如滾動(dòng)條就會(huì)形成自己獨(dú)立的層級(jí):
既然是多層結(jié)構(gòu),那么就需要更新每層的信息,這個(gè)任務(wù)是紫色的Update Layer Tree
:
Paint
我們可以發(fā)現(xiàn),在FP
之前,Update Layer Tree
之后只剩下Paint
這一任務(wù)了:
從字面意義講,這就是繪制么?并不是。
Paint
的任務(wù)是整理每一層頁面的繪制信息,構(gòu)成繪制列表,這些數(shù)據(jù)會(huì)交給合成線程負(fù)責(zé)后續(xù)繪制操作。
可以發(fā)現(xiàn),具體的繪制操作是交由合成線程完成,他與JS
所在線程(主線程)并不是互斥的。
JS為啥阻塞渲染
我們現(xiàn)在知道,JS
執(zhí)行與Paint
任務(wù)都發(fā)生在主線程。
渲染被阻塞的原因很明顯:因?yàn)?code>Paint任務(wù)沒有及時(shí)執(zhí)行,即繪制列表沒有及時(shí)提交給合成線程。
之所以沒有及時(shí)執(zhí)行,可能是因?yàn)?code>JS執(zhí)行時(shí)間過長(zhǎng),導(dǎo)致這一幀沒有時(shí)間執(zhí)行Paint
。
比如,我們打開B站,記錄下主線程的任務(wù)。
可以看到,有個(gè)JS
執(zhí)行時(shí)長(zhǎng)達(dá)到231.88ms,超過了一幀的時(shí)間,在此期間主線程就沒時(shí)間執(zhí)行Paint
了:
總結(jié)
JS
之所以阻塞渲染,是因?yàn)?code>JS執(zhí)行與渲染相關(guān)任務(wù)都在爭(zhēng)奪主線程有限的資源。當(dāng)JS
執(zhí)行時(shí)間過長(zhǎng),渲染相關(guān)任務(wù)就沒時(shí)間執(zhí)行了。
到此這篇關(guān)于深入了解JavaScript阻塞渲染的文章就介紹到這了,更多相關(guān)JS阻塞渲染 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
uniapp實(shí)現(xiàn)滾動(dòng)觸底加載項(xiàng)目實(shí)戰(zhàn)
這篇文章主要為大家介紹了uniapp實(shí)現(xiàn)滾動(dòng)觸底加載項(xiàng)目實(shí)戰(zhàn)方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Bootstarp 基礎(chǔ)教程之表單部分實(shí)例代碼
這篇文章主要介紹了Bootstarp 基礎(chǔ)教程之表單部分實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-02-02js判斷客戶端是iOS還是Android等移動(dòng)終端的方法
本文為大家介紹下使用js判斷客戶端是iOS還是Android等移動(dòng)終端,示例代碼如下,感興趣的朋友可以參考下2013-12-12JS中new?Date().Format("yyyy-MM-dd")?報(bào)錯(cuò)的解決
這篇文章主要介紹了JS中new?Date().Format("yyyy-MM-dd")?報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01javascript經(jīng)典特效分享 手風(fēng)琴、輪播圖、圖片滑動(dòng)
這篇文章主要介紹了javascript經(jīng)典特效,手風(fēng)琴、輪播圖、圖片滑動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09js圖數(shù)據(jù)結(jié)構(gòu)處理 迪杰斯特拉算法代碼實(shí)例
這篇文章主要介紹了js圖數(shù)據(jù)結(jié)構(gòu)處理 迪杰斯特拉算法代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09手把手教你 CKEDITOR 4 實(shí)現(xiàn)Dialog 內(nèi)嵌 IFrame操作詳解
這篇文章主要介紹了手把手教你 CKEDITOR 4 實(shí)現(xiàn)Dialog 內(nèi)嵌 IFrame操作,結(jié)合實(shí)例形式分析了CKEDitor4 Dialog內(nèi)嵌IFrame具體操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下2019-06-06