譯者注:昨天一篇工作僅一年的前端工程師面試幾個大廠的文章 引起了很多人的關(guān)注。一方面大家覺得作者太厲害了,工作近一年,能力竟然這么強(qiáng)(大叔我表示慚愧),另一方面幾個微信面試題引起很多討論。其中我比較感興趣的一個題目是關(guān)于JavaScript和CSS阻塞DOM的。老實話講,我以前也沒太關(guān)注過,與其哀嘆自己研究的不夠深入,不如我們?nèi)W(xué)習(xí)彌補(bǔ)一下吧。這篇文章是我今天讀到的,感覺不錯,翻譯一下給大家共享。
原文地址:https://www.keycdn.com/blog/blocking-the-dom/
原文作者:BRIAN JACKSON
譯者:接灰的電子產(chǎn)品
當(dāng)我們談到web性能或者優(yōu)化頁面級別的速度時,非常重要的一點是要理解HTML和一個頁面是如何在瀏覽器中構(gòu)造的,這樣你才能找到由于渲染阻塞導(dǎo)致的頁面加載延遲。在這篇文章中,我們會深入了解是 什么阻塞了DOM 以及你應(yīng)該怎樣避免這種情況。
什么是DOM?
DOM是Document Object Model(文檔對象模型)的縮寫。它是為HTML和XML定義的一個編程接口,提供了文檔的結(jié)構(gòu)化表示(節(jié)點樹狀結(jié)構(gòu)),同時也規(guī)定了使用腳本編程語言(例如JavaScript)應(yīng)該如何訪問以及操作DOM。這樣一個節(jié)點樹狀結(jié)構(gòu)是由不同的元素、父節(jié)點、子節(jié)點、兄弟節(jié)點等構(gòu)成,它們彼此都有層級化的關(guān)系。下圖是一個HTML DOM的例子:

用人話描述DOM
簡單的講,當(dāng)你使用一個類似Chrome開發(fā)者工具的東東時,你可以看到一個可視化的DOM。你的HTML并不是DOM,但Chrome開發(fā)者工具為你展現(xiàn)了一個經(jīng)過HTML或JavaScript加工之后的DOM。所以你可以把DOM理解成解析后的HTML。

什么在阻塞DOM?
當(dāng)我們分析頁面速度時,我們總要考慮什么阻塞了DOM導(dǎo)致我們的頁面加載出現(xiàn)延遲。這些阻塞因素我們可以叫做 阻塞渲染的資源 ,例如 HTML、CSS(也包括web font)和 JavaScript。
要查看什么阻塞了DOM的最簡單的方法之一就是使用 Chrome開發(fā)者工具
(Chrome DevTools) 和Google的 PageSpeed Insight
。在下面的例子中,我們使用了最新的Chrome開發(fā)者工具 (可以通過 Chrome Canary 獲得)。
- 在Chrome中啟動開發(fā)者工具
- Windows:
F12
或者Ctrl + Shift + I
- Mac:
Cmd + Opt + I
- 切換到
Network
(網(wǎng)絡(luò))面板,刷新頁面( Win:Ctrl + R
, Mac:Cmd + R
) - 現(xiàn)在你會看到一個加載時間瀑布圖。這里有兩個值得我們關(guān)注的東東:第一個是
DOMContentLoaded
是384ms(譯者注:原文如此,看圖的話應(yīng)該是281ms),第二個就是瀑布圖中的在藍(lán)線之前的綠色部分(譯者注:原圖有點問題,藍(lán)線看起來是紫色的)
我們知道CSS和JavaScript都是阻塞渲染的資源,它們都會在藍(lán)色的DOMContent之前加載。請注意,圖像是不會阻塞渲染的 ,所以如果有圖像落在藍(lán)線之前或之上你可以放心的忽略掉,當(dāng)然優(yōu)化圖像也是很重要的一項工作。在這個例子里面,我們可以看到 style.css
和 jquery.min.js
都是阻塞渲染的資源。

你同樣可以通過 Google PageSpeed Insights
(https://developers.google.com/speed/pagespeed/insights/) 工具來驗證我們上面的結(jié)論。下圖中顯示這兩個文件的確是阻塞渲染的。

我們下面要學(xué)習(xí)的是如何 通過優(yōu)化關(guān)鍵渲染路徑來避免CSS和JavaScript阻塞DOM 。盡管HTML也算是一個阻塞渲染的資源(譯者注:記住HTML不是DOM),但DOM是可以增量構(gòu)建的(譯者注:所以優(yōu)化的是CSS和JavaScript,而不是HTML)。
注意,我們無需追求在 Google PageSpeed Insights
的 100/100
。例如,如果你鏈接引用了Google的web字體,那么無論你做什么,這個外部的 fonts.googleapis.com
樣式都始終會是一個阻塞渲染的資源。重要的是當(dāng)你在處理有著10+個阻塞渲染資源的一個大型站點時,要理解清楚什么導(dǎo)致了延遲,有什么樣的策略可以使這些資源可以更有效率的加載。
CSS
非渲染阻塞的CSS
如果你追求一個完全沒有阻塞的CSS,那么你的唯一選項就是:在HTML中內(nèi)聯(lián)嵌入你的CSS。你可以把需要初始渲染的CSS,一般來講就是第一屏的樣式,直接放在 HEAD
里面的 <style></style>
中,然后剩下的CSS放在 </body>
之前。這樣做可以完全避免CSS阻塞渲染。
有幾個可以輔助你完成內(nèi)聯(lián)樣式嵌入的自動化插件
- Grunt: grunt-critical-css
- critical
你當(dāng)然也可以使用JavaScript來加載CSS,但是這樣做會導(dǎo)致頁面在加載結(jié)束時重繪,因此這個選項對于網(wǎng)站訪問者來說不一定會很理想。
在Chrome開發(fā)者工具中可以看到,我們做完內(nèi)聯(lián)樣式優(yōu)化之后的版本中 DOMContentLoaded
減少到了 278ms
:

現(xiàn)在我們再去 Google PageSpeed Insights
測試,會發(fā)現(xiàn)CSS已不是阻塞渲染的資源了。

當(dāng)然這很不錯,但一切取決于你的站點實際情況。大多數(shù)站點并不想內(nèi)聯(lián)嵌入所有的CSS,因為CSS的內(nèi)容多少直接且顯著的影響了頁面下載的大小。對于小型站點或者就是個 Landing Page
,這種情況下內(nèi)聯(lián)嵌入CSS可以是一個不錯的選項,如果你真的想完全避免CSS阻塞渲染的話。
我們的CSS建議
即使在我們自己的KeyCDN主頁上,我們也有一個阻塞渲染的CSS。但是,我們做了其他一些事情來優(yōu)化CSS的加載時間,下面是我們的建議:
- 正確的調(diào)用你的CSS文件 (譯者注:原文如此,感覺應(yīng)該是位置或時機(jī)?)
- 使用
media queries
(媒體查詢) 來標(biāo)記某些CSS為非阻塞資源 (譯者注: 比如<link href="other.css" rel="stylesheet" media="(min-width: 40em)">
這樣可以在其他屏幕尺寸加載時就不用加載這個css了) - 減少CSS的數(shù)量(盡可能放到一個CSS文件中)
- Minify CSS文件(刪除多余的空格、字符、注釋等)
- 盡可能的減少樣式數(shù)量(譯者注:和第三條不同,是減少樣式數(shù)量,不是文件數(shù)量)
一些用于最小化(Minify)CSS的工具
- Grunt: grunt-contrib-cssmin
- Gulp:gulp-minify-css
JavaScript
非渲染阻塞的JavaScript
有一些關(guān)于JavaScript的最佳實踐需要牢記在心:
- 把腳本放在頁面尾部
</body>
之前的位置 - 使用async或defer指令來避免阻塞渲染
異步加載JavaScript
async
允許腳本在后臺下載,因此是無阻塞的。但是當(dāng)下載完成的時刻,渲染又會阻塞了,這是因為腳本執(zhí)行了。當(dāng)腳本執(zhí)行完畢,渲染又恢復(fù)了。
<script async src="foobar.js"></script>
延遲加載JavaScript
defer
指令做的事情和 async
基本一樣,區(qū)別點在于 defer
是嚴(yán)格要求腳本的執(zhí)行順序必須和在HTML中標(biāo)記的順序一樣。所以說,可能存在一種情況,當(dāng)一些腳本已經(jīng)下載完畢,這些腳本不會立即執(zhí)行,它們會等待其他腳本下載完成,因為那些腳本在HTML中出現(xiàn)在它們之前。
Patrick Sexton寫了一篇非常好的博文: 如何延遲加載JavaScript
除去上面兩條,我們對于JavaScript的另外3條建議是:
- 減少JavaScript的數(shù)量(盡量整合成一個JS文件)
- Minify(最小化)JavaScript
- 如果JavaScript很小的話,可以內(nèi)聯(lián)嵌入
用于最小化JavaScript的自動化任務(wù)插件
- Grunt:grunt-contrib-uglify
- Gulp:gulp-uglify
通過把我們的JavaScript移動到頁面尾部,以及使用 async
指令之后,我們把 DOMContentLoaded
顯著的減少到了 144ms
。我們可以看到 jquery.min.js
文件現(xiàn)在出現(xiàn)在了DOM的藍(lán)線之后了。

那么在 Google PageSpeed Insights
中同樣的,由于我們已經(jīng)異步加載了JavaScript,所以這一項的扣分不存在了,我們達(dá)成了 100/100
。

Web Fonts
Web Fonts(Web字體)也被視為一種阻塞渲染的資源,因為它們是通過CSS加載的。你有兩個選擇:阻塞渲染或者延遲重繪(這種情況你需要處理 FOUT)。舉個例子,在Chrome(36以上版本),Opera(23以上版本)和Firefox中有一個 three-second timeout,在超時后,fall-back字體會被使用。
同樣的我們有幾個關(guān)于加載字體和優(yōu)化關(guān)鍵渲染路徑的小建議:
- 使用Web Font加載器或者字體加載API
- 使用內(nèi)聯(lián)嵌入優(yōu)化字體加載
- 使用例如localStorage等存儲方法
關(guān)于更多深入的加載Web Fonts、如何避免渲染阻塞以及FOUT/FOIT等可以查看這篇博文:
analyzing web font performance
小總結(jié)
我們希望到這里你可以對阻塞DOM、DOM樹是如何構(gòu)建的,為何會被CSS和JavaScript阻塞等問題有了一些了解。請再次記住不要追求 Google PageSpeed Insights
的 100/100
,重要的是理解你的渲染阻塞資源是如何阻塞DOM的以及你會怎樣正確的優(yōu)化使得頁面的加載變快。