HTML <script> 腳本的 async 與 defer 屬性及不同屬性的運行時機與 DOMContentLoaded 事件的關系
瀏覽器對于帶有 async, defer 屬性與不攜帶屬性的 <script> 腳本有不同的行為。
它們可以分別翻譯為:異步腳本,延遲腳本與同步(阻塞)腳本。
對于模塊腳本,默認是 defer 的行為,它也能設置 async,以更改瀏覽器的處理方式。
同步腳本
不帶 async 與 defer 屬性的腳本是同步腳本,如果它們出現在文檔頭部及中間任意位置,會阻塞文檔的解析。具體的行為是:
- 文檔停止解析(與渲染)
- 請求腳本資源
- 資源下載完畢后立即解析并執行腳本
- 腳本解析完畢后恢復文檔的解析
為了更好的用戶體驗,對于 HTML 文檔,瀏覽器往往是邊下載邊解析邊渲染,而不是等上一項任務完成后再繼續下一項。
如果請求腳本資源消耗太長時間,或腳本中有同步的耗時任務,在腳本運行結束前,文檔腳本位置之后的內容將不被解析與渲染,出現所謂‘白屏’,這會給人不好的感受。
為了保證文檔的正常解析與渲染,如果使用同步腳本,應該將它們放在 <body> 閉合標簽之前,這樣不管腳本的下載或運行時怎樣的慢,至少文檔基本的內容是可讀的。
關于 DOMContentLoaded 事件,它應該在文檔解析完成后觸發。但是觸發之前,會等待同步腳本與延遲腳本(defer script)運行完畢。
async script (異步腳本)
async script 的下載會另起線程,與文檔的解析是并行的,但它下載完成后會立刻解析并運行腳本,不論文檔是否解析完畢。此時若文檔尚未解析完成,則會阻塞文檔的解析,當腳本運行耗時任務,也將會造成‘白屏’。
但是將 async script 放在文檔的末尾意義不大,畢竟其下載資源時并不阻塞文檔的解析。使用它的目的應該是:
希望腳本盡快執行,并減少對文檔解析的阻塞。
由于 async script 會在下載后立即執行,不能預知哪個腳本會先下載完成,所以它們的運行時機是未知、無序的。
async script 可能會在文檔解析完成之前或之后下載完成并執行,而 DOMContentLoaded 事件的觸發并不會等待 async script 執行完畢,所以并不能知道 async script 會在 DOMContentLoaded 事件觸發之前還是之后運行。基于以上原因,不要在 async script 中依賴別的腳本,也不要在 async script 中為 DOMContentLoaded 事件注冊回調,回調函數能否被調用是未知的。
基于異步腳本的特點,以下場景適合使用:
-
無依賴,不操作DOM,且需要盡快執行的任務,使用 async script 來減少對文檔解析的阻塞。
比如:PV/UV埋點統計
-
在必要的 DOM 加載完成后,盡快為 DOM 元素加載內容、提供交互能力。
由于 HTML 文檔往往是邊下載邊解析邊渲染,可以在網頁首屏的 DOM 加載完畢后引入 async script,操作DOM元素,使用戶可以盡快看到動態加載的內容或與頁面互動。并盡量降低對 HTML 文檔解析的影響
但是除非文檔的數據量很可觀,下載與解析要消耗一些時間(3秒以上),否則這么做往往得不償失。
一般的動態頁面,文檔體積小,解析起來很快(下載到解析完只要幾百毫秒),這么做不但達不到優化目的,阻塞渲染甚至會降低用戶體驗。
使用 async script 的目的是希望腳本盡快運行,可能是希望提升用戶的使用體驗,也可能是為了避免用戶在腳本運行前就離開頁面,而錯過什么必要的任務。
使用它很可能會阻塞文檔的解析,一定要慎重考量后做決定。
使用 async 也增加了維護成本:我們要記住它運行時機是不確定的,有些操作在該腳本中運行可能會無效,還要忍受它要引入依賴就只能在其前面引入同步腳本。
除此之外,在文檔源碼中間插入 <script> 標簽也并不美觀,它不被渲染到視圖,還破壞了文檔的連貫性。
不管出于什么目的,若決定使用 async script,插入到文檔頭部或中間,我們一定要保證腳本內的任務快速地運行完畢,不要做耗時操作,將對文檔解析的阻塞盡量減少。
defer (延遲腳本)
defer script 會開辟新線程下載腳本,并等待文檔解析完畢、所有 defer script 下載完畢后再按腳本在文檔中出現的位置依次執行。
DOMContentLoaded 事件,會等待 defer script 全部執行完畢后才觸發。
defer script 順序加載的特點非常適合一個腳本依賴于另一個腳本的情況。
使用 defer script,并將其放到 <head> 中在大多數情況下都是合適的,這么做和將同步腳本放到 </body> 之前的效果一模一樣,還能讓 <body> 元素保持整潔,畢竟在尾部放一堆不會被渲染的 <script> 看起來不是那么的舒服。

浙公網安備 33010602011771號