首頁  >  文章  >  web前端  >  JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧

WBOY
WBOY原創
2016-05-16 16:39:571164瀏覽

接取的純技術就是對規範的認知

什麼是DOMContentLoaded事件?

首先想到的是查看W3C的HTML5規格,DOMContentLoaded事件何時觸發:

一旦用戶代理停止解析文檔,用戶代理必須執行以下步驟:
1. 將目前文件就緒狀態設為“互動”,並將插入點設為未定義。
將所有節點從打開的元素堆疊中彈出。
2. 如果文件完成解析後將執行的腳本清單不為空,請執行下列子步驟:
2.1 旋轉事件循環,直到文件完成解析時將執行的腳本清單中的第一個腳本設定了「準備好解析器執行」標誌,且解析器的文件沒有阻止腳本的樣式表。
2.2 執行腳本清單中的第一個腳本,該腳本將在文件解析完成後執行。
2.3 從文件解析完成後執行的腳本清單中刪除第一個腳本元素(即移出清單中的第一個項目)。
2.4 如果文件解析完成後執行的腳本清單仍然不為空,則從子步驟 1 開始再次重複這些子步驟。
3. 將任務排隊觸發一個簡單事件,該事件在文件中冒泡名為 DOMContentLoaded

規格總是這麼晦澀難懂,但至少有一點是可以明確的,就是在JS(不包括動態插入的JS)執行完成之後,就會觸發DOMContentLoaded事件。

接下來看看MDN上有關DOMContentLoaded事件的文件

文件完全載入和解析後會觸發 DOMContentLoaded 事件,無需等待樣式表、圖像和子框架完成載入
注意:樣式表載入區塊腳本執行,因此如果在 <font face="NSimsun"><script></script></font> 之後有一個 <font face="NSimsun"><link rel="stylesheet" ...></font>,頁面將無法完成解析 - 並且 DOMContentLoaded 將不會觸發 - 直到載入樣式表。

這麼看來,至少可以這麼一個理論:DOMContentLoaded事件本身不會等待CSS檔案、圖片、iframe載入完成。
它的觸發時機是:載入完成頁面,解析完成所有標籤(不包括執行CSS和JS),並如規範中所說的設定<font face="NSimsun">interactive</font>並執行每個靜態的腳本標籤中的JS,然後觸發。 🎜> 而JS的執行,需要等待位於它前面的CSS載入(如果是外聯的話)、執行完成,因為JS可能會依賴位於它前面的CSS計算出來的樣式。

實踐是檢驗真理的唯一標準

實驗1:DOMContentLoaded事件不直接等待CSS檔案、圖片的載入

index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title></title>
  <link rel="stylesheet" type="text/css" href="./css/main.css" rel="external nofollow" rel="external nofollow" >
</head>
<body>
  <p>Content</p>
  <img  src="./img/chrome-girl.jpg" alt="JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧" >
</body>
</html>

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


圖一

如果頁面中沒有腳本標籤,DOMContentLoaded事件並沒有等待CSS檔案、圖片載入完成。

Chrome開發者工具的時間軸面板可以幫助我們記錄瀏覽器的一舉一動。圖一中紅色小方框中的藍線,表示DOMContentLoaded事件,它右邊的紅線和綠線分別表示load事件和First畫畫,滑鼠懸停在這些線使用灰色方框下面的一半時就會出現標籤說明文字的提示(這交易夠反人類的對吧!)。

實驗2:DOMContentLoaded事件需要等待JS執行完成才觸發

index.html:

<!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" rel="external nofollow" rel="external nofollow" >
  <script type="text/javascript">
    console.timeStamp('Inline script after link in head');
  </script>
</head>
<body>
  <p>Content</p>
  <img  src="./img/chrome-girl.jpg" alt="JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧" >
  <script type="text/javascript" src="./js/main.js"></script>
</body>
</html>

main.js:
console.timeStamp('External script after link in body');

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧

图二

如果页面中静态的写有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中添加的记录也可证明其触发时间。

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图三

现代浏览器会并发的预加载CSS, JS,也就是一开始就并发的请求这些资源,但是,执行CSS和JS的顺序还是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)。先加载完成的资源,如果其依赖还没加载、执行完,就只能等着。

实验3:img何时开始解码、绘制?


从图三中我们可以发现一个有趣的地方:img的请求老早就发出了,但延迟了一段时间才开始解码。如图二、图三中的红框所示,截图中只框出了一部分表示解码的记录,而实际上这些表示解码的记录一直持续到img加载结束,如图四所示,img是一边加载一边解码的:

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图三

现代浏览器会并发的预加载CSS, JS,也就是一开始就并发的请求这些资源,但是,执行CSS和JS的顺序还是按原来的依赖顺序(JS的执行要等待位于其前面的CSS和JS加载、执行完)。先加载完成的资源,如果其依赖还没加载、执行完,就只能等着。

实验3:img何时开始解码、绘制?

从图三中我们可以发现一个有趣的地方:img的请求老早就发出了,但延迟了一段时间才开始解码。如图二、图三中的红框所示,截图中只框出了一部分表示解码的记录,而实际上这些表示解码的记录一直持续到img加载结束,如图四所示,img是一边加载一边解码的:

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图四

抱着“猜想——验证”的想法,我猜想这是因为img这个资源是否需要展现出来,需要等 所有的JS和CSS的执行完 才知道,因为main.js可能会执行某些DOM操作,比如删除这个img元素,或者修改其src属性,而CSS可能会将其 <font face="NSimsun">display: none</font>

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图五

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图六

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图七

图五中没有JS和CSS,img的数据一接收到就马上开始解码了。
图六中没有JS,但img要等到CSS加载完才开始解码。
图七的代码跟图六的代码唯一的区别是CSS把img给 <font face="NSimsun">display: none;</font> ,这使得img虽然请求了,但根本没有进行解码。
这说明,img是否需要解码、绘图(paint)出来,确实需要等CSS加载、执行完才能知道。也就是说,CSS会阻塞img的展现!那么JS呢?

JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧


图八

图八对应的代码:

<!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" alt="JS、CSS以及img對DOMContentLoaded事件的影響_javascript技巧" >
  <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模​​擬慢速網路

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn