天天看點

再談 load 與 DOMContentLoaded

本文首發在github,感興趣請 點選此處

window 的 onload 事件對于前端童鞋來說肯定是熟的不能再熟了,相信大家在剛入門時,見的最多的可能就是 load 事件了。load 事件接觸多了,大家就會接觸到它的閨蜜 DOMContentLoaded 事件,網上有很多介紹這兩個事件的文章,對們它的解釋無外乎以下兩種

  • load

MDN的解釋:load 應該僅用于檢測一個完全加載的頁面

當一個資源及其依賴資源已完成加載時,将觸發load事件。

意思是頁面的html、css、js、圖檔等資源都已經加載完之後才會觸發 load 事件。

  • DOMContentLoaded
MDN的解釋:當初始的 HTML 文檔被完全加載和解析完成之後,DOMContentLoaded 事件被觸發,而無需等待樣式表、圖像和子架構的完成加載。

意思是HTML下載下傳、解析完畢之後就觸發。

看了這兩個解釋,我仍然一臉懵逼,隻是像國小生背課文一樣知道 load 和 DOMContentLoaded 事件的觸發時機,但還是不明白究竟什麼情況下觸發這兩種事件。

一些概念

下載下傳/加載

這兩個詞語表達的是一個意思,就是浏覽器将資源下載下傳到本地的過程。

解析

解析的意思是将一個元素通過一定的方式轉換成另一種形式。

比如 html 的解析。首先要明确,html 下載下傳到浏覽器的表現形式就是 包含字元串的檔案。浏覽器将 html 檔案裡面的字元串讀取到記憶體中,按照 html 規則,對字元串進行取詞編譯,将字元串轉化成另一種易于表達的資料結構。我們看下一段代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>隻有css</title>
  <link rel="stylesheet" href="./index.css" />
</head>
<body>
  <div id="div1"></div>
  <link rel="stylesheet" href="./c1.css" />
  <link rel="stylesheet" href="./c3.css" />
  <script src="http://test.com:9000/mine/load/case2/j1.js
  "></script>
  <link rel="stylesheet" href="./c4.css" />
  <div id="div2"></div>
</body>
</html>
           

浏覽器會對這個 html 檔案進行編譯,轉化成類似下面的結構(這裡把 head 中的其他标簽省略了)。

再談 load 與 DOMContentLoaded

浏覽器會對轉化後的資料結構自上而下進行分析:首先開啟下載下傳線程,對所有的資源進行優先級排序下載下傳(注意,這裡僅僅是下載下傳)。同時主線程會對文檔進行解析:

  • 遇到 script 标簽時,首先阻塞後續内容的解析,同時檢查該script是否已經下載下傳下來,如果已下載下傳,便執行代碼。
  • 遇到 link 标簽時,不會阻塞後續内容的解析(比如 DOM 建構),檢查 link 資源是否已下載下傳,如果已下載下傳,則建構 cssom。
  • 遇到 DOM 标簽時,執行 DOM 建構,将該 DOM 元素添加到文檔樹中。
有一點要注意的是,在 body 中第一個 script 資源下載下傳完成之前,浏覽器會進行首次渲染,将該 script 标簽前面的 DOM 樹和 CSSOM 合并成一棵 Render 樹,渲染到頁面中。這是頁面從白屏到首次渲染的時間節點,比較關鍵。

DOM 建構

DOM 建構的意思是,将文檔中的所有 DOM 元素建構成一個樹型結構。

注意,DOM 建構是自上而下進行建構的,會受到 js 執行的幹擾。

CSS 建構

将文檔中的所有 css 資源合并。

render 樹

将 DOM 樹和 CSS 合并成一棵渲染樹,render 樹在合适的時機會被渲染到頁面中。(比如遇到 script 時, 該 script 還沒有下載下傳到本地時)。

HTML文檔的加載與頁面的首次渲染

當我們輸入一個頁面位址時,發生了哪些事情呢?

  • 1、浏覽器首先下載下傳該位址所對應的 html 頁面。
  • 2、浏覽器解析 html 頁面的 DOM 結構。
  • 3、開啟下載下傳線程對文檔中的所有資源按優先級排序下載下傳。
  • 4、主線程繼續解析文檔,到達 head 節點 ,head 裡的外部資源無非是外鍊樣式表和外鍊 js。
    • 發現有外鍊 css 或者外鍊 js,如果是外鍊 js ,則停止解析後續内容,等待該資源下載下傳,下載下傳完後立刻執行。如果是外鍊 css,繼續解析後續内容。
  • 5、解析到 body
    body 裡的情況比較多,body 裡可能隻有 DOM 元素,可能既有 DOM、也有 css、js 等資源,js 資源又有可能異步加載 圖檔、css、js 等。DOM 結構不同,浏覽器的解析機制也不同,我們分開來讨論。
    • 隻有 DOM 元素
      • 這種情況比較簡單了,DOM 樹建構完,頁面首次渲染。
    • 有 DOM 元素、外鍊 js。
      • 當解析到外鍊 js 的時候,該 js 尚未下載下傳到本地,則 js 之前的 DOM 會被渲染到頁面上,同時 js 會阻止後面 DOM 的建構,即後面的 DOM 節點并不會添加到文檔的 DOM 樹中。是以,js 執行完之前,我們在頁面上看不到該 js 後面的 DOM 元素。
    • 有 DOM 元素、外鍊 css
      • 外鍊 css 不會影響 css 後面的 DOM 建構,但是會阻礙渲染。簡單點說,外鍊 css 加載完之前,頁面還是白屏。
    • 有 DOM 元素、外鍊 js、外鍊 css
      • 外鍊 js 和外鍊 css 的順序會影響頁面渲染,這點尤為重要。當 body 中 js 之前的外鍊 css 未加載完之前,頁面是不會被渲染的。
      • 當body中 js 之前的 外鍊 css 加載完之後,js 之前的 DOM 樹和 css 合并渲染樹,頁面渲染出該 js 之前的 DOM 結構。
  • 6、文檔解析完畢,頁面重新渲染。當頁面引用的所有 js 同步代碼執行完畢,觸發 DOMContentLoaded 事件。
  • 7、html 文檔中的圖檔資源,js 代碼中有異步加載的 css、js 、圖檔資源都加載完畢之後,load 事件觸發。

測試代碼如下

<body>
  <!-- 白屏 -->
  <div id="div1"></div>
  <!-- 白屏 -->
  <link rel="stylesheet" href="./c1.css" />
  <!-- 白屏 -->
  <link rel="stylesheet" href="./c3.css" />
  <!-- 如果此時 j1.js 尚未下載下傳到本地,則首次渲染,此時的 DOM 樹 隻有 div1 ,是以頁面上隻會顯示 div1,樣式是 c1.css 和 c3.css 的并集。-->
  <!-- 如果此時 j1.js 已經下載下傳到本地,則先執行 j1.js,頁面不會渲染,是以此時仍然是白屏。-->
  <!--下面的 js 阻塞了 DOM 樹的建構,是以下面的 div2 沒有在文檔的 DOM 樹中。 -->
  <script src="http://test.com:9000/mine/load/case2/j1.js
  "></script>
  <!-- j1.js 執行完畢,繼續 DOM 解析,div2 被建構在文檔 DOM 樹中,此時頁面上有了div2 元素,樣式仍然是 c1.css 和 c3.css 的并集 -->
  <link rel="stylesheet" href="./c4.css" />
  <!-- c4.css 加載完畢,重新建構render樹,樣式變成了 c1.css、c3.css 和 c4.css 的并集 -->
  <div id="div2"></div>
  <script>
  // 利用 performance 統計 load 加載時間。
    window.onload=function(){console.log(performance.timing.loadEventStart - performance.timing.fetchStart);}
  </script>
</body>
           

大家可以調整資源擺放位置,觀察浏覽器的解析表現。

head 中資源的加載

  • head 中 js 資源加載都會停止後面 DOM 的建構,但是不影響後面資源的下載下傳。
  • css資源不會阻礙後面 DOM 的建構,但是會阻礙頁面的首次渲染。

body 中資源的加載

  • body 中 js 資源加載都會停止後面 DOM 的建構,但是不影響後面資源的下載下傳。
  • css 資源不會阻礙後面 DOM 的建構,但是會阻礙頁面的首次渲染。

DomContentLoaded 事件的觸發

上面隻是講了 html 文檔的加載與渲染,并沒有講 DOMContentLoaded 事件的觸發時機。直截了當地結論是,DOMContentLoaded 事件在 html文檔加載完畢,并且 html 所引用的内聯 js、以及外鍊 js 的同步代碼都執行完畢後觸發。

大家可以自己寫一下測試代碼,分别引用内聯 js 和外鍊 js 進行測試。

load 事件的觸發

當頁面 DOM 結構中的 js、css、圖檔,以及 js 異步加載的 js、css 、圖檔都加載完成之後,才會觸發 load 事件。

注意:

  • 頁面中引用的js 代碼如果有異步加載的 js、css、圖檔,是會影響 load 事件觸發的。
  • video、audio、flash 不會影響 load 事件觸發。

大家可以在 chrome 中試一下。

浏覽器對同一域名下的資源并發下載下傳線程數,chrome為6個。

  • 浏覽器對同一域名下的下載下傳并發不超過 6 個。超過 6 個的話,剩餘的将會在隊列中等待,這就是為什麼我們要将資源收斂到不同的域名下,也是為了充分利用該機制,最大程度的并發下載下傳所需資源,盡快的完成頁面的渲染。
這裡要注意關鍵詞:同一域名。如果 n 個不同域名的話,在浏覽器設定的最大并發上限以内(預設是10個),是可以達到 n * 6 個的最大并發的下載下傳的。

performance性能統計。

另外,load 事件與 DOMContentLoaded 事件觸發所花費的時間,可以利用 performance 這個對象的一些屬性進行統計,時間精确到納秒級。一些大公司的性能統計也主要利用這個對象的資料進行上報。

再談 load 與 DOMContentLoaded
  • connectStart:HTTP(TCP)開始建立連接配接的時間。如果是持久連接配接,則和 fetchStart 的時間相等,注意,如果在傳輸層發生了錯誤且重建立立連接配接,這裡顯示的是建立立連接配接的開始時間。
  • connectEnd: 完成建立連接配接的時間。
  • domComplete:DOM 樹解析完成,并且資源準備就緒的時間,Document.readyState 變為 complete,并将抛出 readystatechange 相關事件。
  • domContentLoadedEventEnd:DOM 解析完成後,網頁内資源加載完成的時間(如 JS、css 加載執行完畢)。
  • domContentLoadedEventStart:DOM 解析完成後,網頁内資源加載開始的時間在 DOMContentLoaded 事件抛出前發生。
  • loadEventStart:load 事件觸發,也即 load 回調函數開始執行的時間。注意:如果沒有綁定 load 事件,值為 0。
  • loadEventEnd:load 事件的回調函數執行完畢的時間。
  • ...

還有一些其他的性能統計屬性,大家可以研究下。

以上内容希望能給大家帶來不一樣的思考,并希望能幫到大家了解文檔的加載機理。

原文釋出時間為:2018年06月21日

原文作者:lucefer

本文來源:

掘金

如需轉載請聯系原作者

繼續閱讀