最近在作性能有關的數據上報,發現了兩個很是有意思的東西:Chrome開發者工具的Timeline分析面板,以及DOMContentLoaded事件。一個是強大的使人髮指的性能分析工具,一個是重要的性能指標,因而就用Timeline對DOMContentLoaded事件進行了一番研究。javascript
前端的純技術就是對規範的認知
什麼是DOMContentLoaded事件?css
首先想到的是查看W3C的HTML5規範,DOMContentLoaded事件在何時觸發:html
Once the user agent stops parsing the document, the user agent must run the following steps:
1. Set the current document readiness to 「interactive」 and the insertion point to undefined.
Pop all the nodes off the stack of open elements.
2. If the list of scripts that will execute when the document has finished parsing is not empty, run these substeps:
2.1 Spin the event loop until the first script in the list of scripts that will execute when the document has finished parsing has its 「ready to be parser-executed」 flag set and the parser’s Document has no style sheet that is blocking scripts.
2.2 Execute the first script in the list of scripts that will execute when the document has finished parsing.
2.3 Remove the first script element from the list of scripts that will execute when the document has finished parsing (i.e. shift out the first entry in the list).
2.4 If the list of scripts that will execute when the document has finished parsing is still not empty, repeat these substeps again from substep 1.
3. Queue a task to fire a simple event that bubbles named DOMContentLoaded at the Document.前端
規範老是那麼的晦澀,但至少有一點是能夠明確了的,就是在JS(不包括動態插入的JS)執行完以後,纔會觸發DOMContentLoaded事件。html5
接下來看看MDN上有關DOMContentLoaded事件的文檔:java
The DOMContentLoaded event is fired when the document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading
Note: Stylesheet loads block script execution, so if you have a<script>
after a<link rel="stylesheet" ...>
, the page will not finish parsing – and DOMContentLoaded will not fire – until the stylesheet is loaded.node
這麼看來,至少能夠得出這麼一個理論:DOMContentLoaded事件自己不會等待CSS文件、圖片、iframe加載完成。
它的觸發時機是:加載完頁面,解析完全部標籤(不包括執行CSS和JS),並如規範中所說的設置interactive
和執行每一個靜態的script標籤中的JS,而後觸發。
而JS的執行,須要等待位於它前面的CSS加載(若是是外聯的話)、執行完成,由於JS可能會依賴位於它前面的CSS計算出來的樣式。chrome
實踐是檢驗真理的惟一標準
實驗1:DOMContentLoaded事件不直接等待CSS文件、圖片的加載完成
index.html:windows
1
2
3
4
5
6
7
8
9
10
11
12
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" type="text/css" href="./css/main.css">
</head>
<body>
<p>Content</p>
<img src="./img/chrome-girl.jpg">
</body>
</html>
|
若是頁面中沒有script標籤,DOMContentLoaded事件並無等待CSS文件、圖片加載完成。
Chrome開發者工具的Timeline面板能夠幫咱們記錄下瀏覽器的一舉一動。圖一中紅色小方框中的藍線,表示DOMContentLoaded事件,它右邊的紅線和綠線分別表示load事件和First paint,鼠標hover在這些線露出灰色方框下面的一小部分時就會出現帶有說明文字的tips(這交互夠反人類的對吧!)。
實驗2:DOMContentLoaded事件須要等待JS執行完才觸發
index.html:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
console.timeStamp('Inline script before link in head');
window.addEventListener('DOMContentLoaded', function(){
console.timeStamp('DOMContentLoaded event');
});
</script>
<link rel="stylesheet" type="text/css" href="./css/main.css">
<script type="text/javascript">
console.timeStamp('Inline script after link in head');
</script>
</head>
<body>
<p>Content</p>
<img src="./img/chrome-girl.jpg">
<script type="text/javascript" src="./js/main.js"></script>
</body>
</html>
|
main.js:
1
|
console.timeStamp('External script after link in body');
|
圖二
若是頁面中靜態的寫有script標籤,DOMContentLoaded事件須要等待JS執行完才觸發。
而script標籤中的JS須要等待位於其前面的CSS的加載完成。
console.timeStamp()
能夠向Timeline中添加一條記錄,並對應上方的一條黃線。
從圖二中能夠看出,在CSS以前的JS馬上獲得了執行,而在CSS以後的JS,須要等待CSS加載完後才執行,比較明顯的是main.js早就加載完了,但仍是要等main.css加載完才能執行。而DOMContentLoaded事件,則是在JS執行完後才觸發。滑動Timeline面板中表示展現區域的滑塊,如圖三,放大後便可看到表示DOMContentLoaded事件的藍線(以前跟黃線和綠線靠的太近了),固然,經過console.timeStamp()
向TimeLine中添加的記錄也可證實其觸發時間。
現代瀏覽器會併發的預加載CSS, JS,也就是一開始就併發的請求這些資源,可是,執行CSS和JS的順序仍是按原來的依賴順序(JS的執行要等待位於其前面的CSS和JS加載、執行完)。先加載完成的資源,若是其依賴還沒加載、執行完,就只能等着。
實驗3:img什麼時候開始解碼、繪製?
從圖三中咱們能夠發現一個有趣的地方:img的請求老早就發出了,但延遲了一段時間纔開始解碼。如圖2、圖三中的紅框所示,截圖中只框出了一部分表示解碼的記錄,而實際上這些表示解碼的記錄一直持續到img加載結束,如圖四所示,img是一邊加載一邊解碼的:
抱着「猜測——驗證」的想法,我猜測這是由於img這個資源是否須要展示出來,須要等 全部的JS和CSS的執行完 才知道,由於main.js可能會執行某些DOM操做,好比刪除這個img元素,或者修改其src屬性,而CSS可能會將其 display: none
。
圖五中沒有JS和CSS,img的數據一接收到就立刻開始解碼了。
圖六中沒有JS,但img要等到CSS加載完纔開始解碼。
圖七的代碼跟圖六的代碼惟一的區別是CSS把img給 display: none;
,這使得img雖然請求了,但根本沒有進行解碼。
這說明,img是否須要解碼、繪圖(paint)出來,確實須要等CSS加載、執行完才能知道。也就是說,CSS會阻塞img的展示!那麼JS呢?
圖八對應的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
console.timeStamp('Inline script in head');
window.addEventListener('DOMContentLoaded', function(){
console.timeStamp('DOMContentLoaded event');
});
</script>
</head>
<body>
<p>Content</p>
<img src="./img/chrome-girl.jpg">
<script type="text/javascript" src="./js/main.js"></script>
</body>
</html>
|
很是使人驚訝,在有JS而沒有CSS的頁面中,img竟然可以在收到數據後就馬上開始解碼、繪圖(paint),也就是說,JS並無阻塞img的展示!這跟咱們之前理解的JS會阻塞img資源的傳統觀念不太同樣,看來Chrome對img的加載和展示作了新的優化。
咱們經常使用的jQuery的 $(document).ready()
方法,就是對DOMContentLoaded事件的監聽(固然,其內部還會經過模擬DOMContentLoaded事件和監聽onload事件來提供降級方案)。一般推薦在DOMContentLoaded事件觸發的時候爲DOM元素註冊事件。因此儘快的讓DOMContentLoaded事件觸發,就意味着可以儘快讓頁面可交互:
- 減少CSS文件體積,把單個CSS文件分紅幾個文件以並行加載,減小CSS對JS的阻塞時間
- 次要的JS文件,經過動態插入script標籤來加載(動態插入的script標籤不阻塞DOMContentLoaded事件的觸發)
- CSS中使用的精靈圖,能夠利用對img的預加載,放在html中跟CSS文件一塊兒加載
在作實驗的過程當中,感受Chrome開發者工具的Timeline面板很是強大,瀏覽器的一舉一動都記錄下來。之前咱們前端開發要想理解、探索瀏覽器的內部行爲,或者摸着石頭過河的作黑盒測試,或者事倍功半的研究瀏覽器源碼,惟一高效點的作法就是學習別人的研究經驗,看老外的文章,但瀏覽器的發展突飛猛進(好比此次實驗發現的JS不阻塞img的展示),別人的經驗始終不是最新、最適合的,關鍵是要結合本身的業務、需求場景,有針對性的作分析和優化。
PS.
以上測試環境爲windows/chrome,並用Fiddler模擬慢速網絡