再談 load 與 DOMContentLoaded

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

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

  • load

MDN的解釋:load 應該僅用於檢測一個徹底加載的頁面 當一個資源及其依賴資源已完成加載時,將觸發load事件。html

意思是頁面的html、css、js、圖片等資源都已經加載完以後纔會觸發 load 事件。前端

  • DOMContentLoaded

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

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

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

一些概念

下載/加載

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

解析

解析的意思是將一個元素經過必定的方式轉換成另外一種形式。 好比 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 中的其餘標籤省略了)。數據結構

瀏覽器會對轉化後的數據結構自上而下進行分析:首先開啓下載線程,對全部的資源進行優先級排序下載(注意,這裏僅僅是下載)。同時主線程會對文檔進行解析:

  • 遇到 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文檔的加載與頁面的首次渲染

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

  • 一、瀏覽器首先下載該地址所對應的 html 頁面。
  • 二、瀏覽器解析 html 頁面的 DOM 結構。
  • 三、開啓下載線程對文檔中的全部資源按優先級排序下載。
  • 四、主線程繼續解析文檔,到達 head 節點 ,head 裏的外部資源無非是外鏈樣式表和外鏈 js。
    • 發現有外鏈 css 或者外鏈 js,若是是外鏈 js ,則中止解析後續內容,等待該資源下載,下載完後馬上執行。若是是外鏈 css,繼續解析後續內容。
  • 五、解析到 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 結構。
  • 六、文檔解析完畢,頁面從新渲染。當頁面引用的全部 js 同步代碼執行完畢,觸發 DOMContentLoaded 事件。
  • 七、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 這個對象的一些屬性進行統計,時間精確到納秒級。一些大公司的性能統計也主要利用這個對象的數據進行上報。

  • 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 事件的回調函數執行完畢的時間。
  • ...

還有一些其餘的性能統計屬性,你們能夠研究下。

以上內容但願能給你們帶來不同的思考,並但願能幫到你們理解文檔的加載機理。

相關文章
相關標籤/搜索