defer和async是script標簽的兩個屬性,用于在不阻塞頁面文檔解析的前提下,控制腳本的下載和執行。
我們先來了解頁面文檔的渲染機制
- 瀏覽器通過HTTP協議請求服務器,獲取HMTL文檔并開始從上到下解析,構建DOM。
- 在構建DOM過程中,如果遇到外聯的樣式聲明和腳本聲明,則暫停文檔解析,創建新的網絡連接,并開始下載樣式文件和腳本文件。
- 樣式文件下載完成后,構建CSSDOM;腳本文件下載完成后,解釋并執行,然后繼續解析文檔構建DOM 。
- 完成文檔解析后,將DOM和CSSDOM進行關聯和映射,最后將視圖渲染到瀏覽器窗口。
在這個過程中,JS腳本文件的下載和執行是與文檔解析同步進行的,如果JS出現堵塞,將會影響頁面渲染,出現白屏、FOUC等現象,影響用戶體驗。因此我們需要使用defer和async來控制腳本異步加載。
defer和async的作用如下:
- defer:用于開啟新的線程下載腳本文件,并使腳本在文檔解析完成后執行。
- async:用于異步下載腳本文件,下載完畢立即解釋執行代碼。
下圖可以更清楚地闡述defer和async的執行以及和DOMContentLoaded、load事件的關系:
綠色線是頁面解析
藍色線是JS下載
紅色線是JS執行
我們可以明顯看到defer和async的腳本下載都是異步進行的,而兩者區別是defer要在頁面解析完成后執行腳本,async是下載完腳本立刻執行腳本,同時async會影響腳本的解析。
下面是defer和async基本語法:
<script async src="script.js"></script>
<script defer src="script.js"></script>
關于defer我們需要注意下面幾點:
- defer只適用于外聯腳本,如果script標簽沒有指定src屬性,只是內聯腳本,不要使用defer;
- 如果有多個聲明了defer的腳本,則會按順序下載和執行 ;
- defer腳本會在DOMContentLoaded和load事件之前執行。
關于async,也需要注意以下幾點:
- 只適用于外聯腳本,這一點和defer一致;
- 如果有多個聲明了async的腳本,其下載和執行也是異步的,不能確保彼此的先后順序;
- async會在load事件之前執行,但并不能確保與DOMContentLoaded的執行先后順序 。