1. 程式人生 > >async、defer與DOMContentLoaded的執行先後關係

async、defer與DOMContentLoaded的執行先後關係

一、HTML解析過程與DOMContentLoaded觸發時機

我們已經知道DOMContentLoaded的觸發時間為:當 HTML文件被載入和解析完成。那麼我們還需要理解HTML的解析過程。

此處我們先只考慮同步js的情況。

1.在既沒有CSS也沒有JS的情況下,HTML文件的解析過程為:

DOMContentLoaded事件的觸發時機為:HTML解析為DOM之後。

2.有CSS無JS的情況下,HTML文件解析過程為:

這裡與1.不同的地方在於,渲染樹的生成是基於DOM和CSSOM的。但是觸發DOMContentLoaded的時間依然是在HTML解析為DOM後,無論此時CSS解析為CSSOM的過程是否完成。

3.當有JS時,HTML文件解析過程為:

有一個問題:關於首屏時間?

“計算這個網頁從空白到出現內容所花費的時間”。那怎麼計算這段時間?這段時間其實就是HTML 文件載入和解析的時間。也就是DOMContentLoaded 事件觸發之前所經歷的時間。

所以,對於首屏時間而言,js放在HTML文件的開頭和結尾處效果是一樣的而js放在結尾的目的並不是為了減少首屏時間,而是由於js經常需要操縱DOM,放在後面才更能保證找到DOM節點。待進一步探究

二、非同步指令碼、延遲指令碼與DOMContentLoaded的關係

sync

為了與非同步指令碼和延遲指令碼進行一個更清晰的對比,在這裡先將同步指令碼的情況分析一下。

如上圖所示, HTML 文件被解析時如果遇見(同步)指令碼,則停止解析,先去載入指令碼,然後執行,執行結束後繼續解析 HTML 文件。HTML文件解析完畢後觸發DOMContentLoaded。

async

對此,《JavaScript高階程式設計》一書的解釋是:帶async的指令碼一定會在load事件之前執行,可能會在DOMContentLoaded之前或之後執行。

為什麼async指令碼可能會在DOMContentLoaded之前或之後執行呢?或者說,為什麼DOMContentLoaded事件的觸發既可能在async指令碼執行前、又可能在async指令碼執行後呢? 這是因為,async 標籤的指令碼載入完畢的時間有兩種情況:

情況1: HTML 還沒有被解析完的時候,async指令碼已經載入完了,那麼 HTML 停止解析,去執行指令碼,指令碼執行完畢後觸發DOMContentLoaded事件。如下圖所示:

情況2: HTML 解析完了之後,async指令碼才載入完,然後再執行指令碼,那麼在HTML解析完畢、async指令碼還沒載入完的時候就觸發DOMContentLoaded事件。如下圖所示:

總之, DomContentLoaded 事件只關注 HTML 是否被解析完,而不關注 async 指令碼

defer

如果 script 標籤中包含 defer,那麼這一塊指令碼將不會影響 HTML 文件的解析,而是等到 HTML 解析完成後才會執行。而 DOMContentLoaded 只有在 defer 指令碼執行結束後才會被觸發。

defer指令碼同樣包含兩種情況:

情況1:HTML還沒解析完成時,defer指令碼已經載入完畢,那麼defer指令碼將等待HTML解析完成後再執行。defer指令碼執行完畢後觸發DOMContentLoaded事件。如下圖所示

情況2:HTML解析完成時,defer指令碼還沒載入完畢,那麼defer指令碼繼續載入,載入完成後直接執行,執行完畢後觸發DOMContentLoaded事件。如下圖所示:

注意defer情況2與async情況2的兩個圖非常相似,區別就在於DOMContentLoaded事件的觸發時間點。

對於defer指令碼,《JavaScript高階程式設計》一書的說法是:“按照h5規範,兩個defer指令碼會安裝它們出現的先後順序執行,兩個指令碼會在DOMContentLoaded之前執行。”這和我們上面的分析一致。然而,該書接下來說,“但事實上,defer指令碼不一定會按順序執行,也不一定會在DOMContentLoaded之前執行。”這是一個待再繼續研究測試的問題