1. 程式人生 > >淺談script標籤中的async和defer

淺談script標籤中的async和defer

script標籤用於載入指令碼與執行指令碼,在前端開發中可以說是非常重要的標籤了。
直接使用script指令碼的話,html會按照順序來載入並執行指令碼,在指令碼載入&執行的過程中,會阻塞後續的DOM渲染。

現在大家習慣於在頁面中引用各種的第三方指令碼,如果第三方服務商出現了一些小問題,比如延遲之類的,就會使得頁面白屏。
好在script提供了兩種方式來解決上述問題,asyncdefer,這兩個屬性使得script都不會阻塞DOM的渲染。
但既然會存在兩個屬性,那麼就說明,這兩個屬性之間肯定是有差異的。

defer

如果script標籤設定了該屬性,則瀏覽器會非同步的下載該檔案並且不會影響到後續DOM

的渲染;
如果有多個設定了deferscript標籤存在,則會按照順序執行所有的script
defer指令碼會在文件渲染完畢後,DOMContentLoaded事件呼叫前執行。

我們做了一個測試頁面,頁面中包含了兩個script標籤的載入,給他們都加上defer標識。
P.S. 為了更直觀,我們給script1.js添加了1s的延遲,給script2.js添加了2s的延遲。
image
下圖是頁面載入的過程&script指令碼的輸出順序。
不難看出,雖然script1載入用時雖然比script2短,但因為defer的限制,所以Ta只能等前邊的指令碼執行完畢後才能執行。
image
image

async

async

的設定,會使得script指令碼非同步的載入並在允許的情況下執行
async的執行,並不會按著script在頁面中的順序來執行,而是誰先載入完誰執行。

我們修改測試頁面如下:
image
遂得到了如下的結果,頁面載入時長上,並沒有什麼變化,畢竟都是非同步載入的指令碼。
但是我們可以看到一個小細節,DOMContentLoaded事件的觸發並不受async指令碼載入的影響,在指令碼載入完之前,就已經觸發了DOMContentLoaded
image
image
image
image

我們接著修改測試頁面。載入一個沒有延遲的script指令碼,使得指令碼可以即時的載入完畢。
我們要測試一下,如果async指令碼載入的足夠快,是否會在DOMContentLoaded

之前就執行(這個實驗是基於對async的描述“在允許的情況下執行”的論證)。
同時為了保證測試的穩定性,我們在script指令碼引入的後邊添加了數千個空的div節點,用來延長文件的渲染時間。
image
執行結果不出所料,如果給async一定的時間,是有可能在DOMContentLoaded事件之前就執行的。
image
P.S. 從上圖中左上角的火焰圖中,我們也能看到,出現了多段的藍色(更新:晚上寫的時候懵了,紫色的才是渲染,藍色的是解析)文件渲染。以及下邊Console的順序。
說明的確,async的執行是載入完成就會去執行,而不像defer那樣要等待所有的指令碼載入完後按照順序執行。

畫幾張圖簡要說明

網上有了不少這種類似的圖,但是基本都是拿一個script就舉例的
未免太過寒酸,so咱們來一個豪華版,來畫一下多個指令碼載入時的甘特圖
就像近年來各大手機廠商,出新機都喜歡來一個X+X plus

拿四個不同的顏色來標明各自代表的含義

更正:文件渲染 應該為 文件解析
image

普通script

文件解析的過程中,如果遇到script指令碼,就會停止頁面的解析進行下載(但是Chrome會做一個優化,如果遇到script指令碼,會快速的檢視後邊有沒有需要下載其他資源的,如果有的話,會先下載那些資源,然後再進行下載script所對應的資源,這樣能夠節省一部分下載的時間 @Update: 2018-08-17)。
資源的下載是在解析過程中進行的,雖說script1指令碼會很快的載入完畢,但是他前邊的script2並沒有載入&執行,所以他只能處於一個掛起的狀態,等待script2執行完畢後再執行。
當這兩個指令碼都執行完畢後,才會繼續解析頁面。
image

defer

文件解析時,遇到設定了defer的指令碼,就會在後臺進行下載,但是並不會阻止文件的渲染,當頁面解析&渲染完畢後。
會等到所有的defer指令碼載入完畢並按照順序執行,執行完畢後會觸發DOMContentLoaded事件。
image

async

async指令碼會在載入完畢後執行。
async指令碼的載入不計入DOMContentLoaded事件統計,也就是說下圖兩種情況都是有可能發生的

image
image

推薦的應用場景

defer

如果你的指令碼程式碼依賴於頁面中的DOM元素(文件是否解析完畢),或者被其他指令碼檔案依賴。
例:

  1. 評論框
  2. 程式碼語法高亮
  3. polyfill.js

async

如果你的指令碼並不關心頁面中的DOM元素(文件是否解析完畢),並且也不會產生其他指令碼需要的資料。
例:

  1. 百度統計

如果不太能確定的話,用defer總是會比async穩定。。。

參考資料

測試程式碼存放處:https://github.com/Jiasm/research-script-tag
clone後執行npm start即可執行。
除錯推薦使用chrome無痕模式(這樣才不會在Performance頁簽上看到不相關的外掛資料)。