在前端開發中,<script>
標簽的 async
和 defer
屬性會顯著影響 JavaScript 腳本的加載和執行時機。下面結合示例代碼,詳細解析它們之間的區別:
1. 默認情況(無 async
/defer
)
<script src="script.js"></script>
特性
- 阻塞渲染:瀏覽器遇到
<script>
標簽后,會立即暫停 HTML 解析,下載并執行腳本,直到腳本執行完畢才繼續解析 HTML。 - 執行順序:按照標簽在 HTML 中的出現順序執行。
- 適用場景:需要立即執行的腳本,或依賴于當前 DOM 的腳本。
示例
<!DOCTYPE html>
<html>
<head><title>默認腳本加載</title>
</head>
<body><div id="app">加載中...</div><!-- 阻塞渲染,直到 script.js 下載并執行完畢 --><script src="script.js"></script><div>其他內容</div>
</body>
</html>
2. async
屬性
<script async src="script.js"></script>
特性
- 異步下載:腳本的下載與 HTML 解析同時進行,不會阻塞渲染。
- 立即執行:腳本下載完成后,會立即暫停 HTML 解析并執行腳本。
- 執行順序不確定:多個
async
腳本的執行順序取決于下載完成的時間,與標簽順序無關。 - 適用場景:獨立的、不依賴于其他腳本或 DOM 的異步腳本(如廣告、分析工具)。
示例
<!DOCTYPE html>
<html>
<head><title>async 腳本加載</title>
</head>
<body><div id="app">加載中...</div><!-- 不阻塞渲染,下載完成后立即執行 --><script async src="analytics.js"></script><div>其他內容</div>
</body>
</html>
3. defer
屬性
<script defer src="script.js"></script>
特性
- 異步下載:腳本的下載與 HTML 解析同時進行,不會阻塞渲染。
- 延遲執行:腳本會在 HTML 解析完成后、
DOMContentLoaded
事件觸發前執行。 - 執行順序確定:多個
defer
腳本按標簽在 HTML 中的出現順序執行。 - 適用場景:需要操作 DOM 或依賴于其他腳本的庫(如 jQuery)。
示例
<!DOCTYPE html>
<html>
<head><title>defer 腳本加載</title><!-- defer 腳本會在 HTML 解析完成后按順序執行 --><script defer src="jquery.js"></script><script defer src="app.js"></script>
</head>
<body><div id="app">內容</div>
</body>
</html>
ps:
在瀏覽器解析 HTML 的過程中,defer
腳本的執行先于 DOMContentLoaded
事件。
執行順序原理
-
defer
腳本的特性:- 帶有
defer
屬性的腳本會與 HTML 解析并行下載(不阻塞解析)。 - 下載完成后不會立即執行,而是等待整個 HTML 文檔解析完成(即 DOM 構建完成)后,再按腳本在 HTML 中的順序依次執行。
- 帶有
-
DOMContentLoaded
事件的觸發時機:- 當整個 HTML 文檔解析完成(DOM 樹構建完畢),且所有
defer
腳本執行完成后,才會觸發DOMContentLoaded
事件。
- 當整個 HTML 文檔解析完成(DOM 樹構建完畢),且所有
示例驗證
<!DOCTYPE html>
<html>
<head><!-- defer 腳本:在 HTML 解析完成后執行 --><script defer src="defer-script.js"></script><script>// 監聽 DOMContentLoaded 事件document.addEventListener('DOMContentLoaded', () => {console.log('DOMContentLoaded 事件觸發');});</script>
</head>
<body><h1>測試執行順序</h1>
</body>
</html>
// defer-script.js
console.log('defer 腳本執行');
輸出順序:
defer 腳本執行
DOMContentLoaded 事件觸發
結論
defer
腳本的執行先于 DOMContentLoaded
事件。
defer
的設計初衷就是保證腳本在 DOM 就緒后、DOMContentLoaded
之前執行,適合需要操作 DOM 且依賴執行順序的場景(如加載 jQuery 后再執行依賴它的代碼)。
執行時機對比圖
HTML 解析 |---------------------------->|
默認腳本 | 下載并執行 |
async 腳本 | 下載 | 執行 |
defer 腳本 | 下載 | | 執行
DOMContentLoaded |---------------------------->|
關鍵區別總結
特性 | 默認(無屬性) | async | defer |
---|---|---|---|
是否阻塞渲染 | 是 | 否 | 否 |
下載時機 | HTML 解析暫停 | 與 HTML 解析并行 | 與 HTML 解析并行 |
執行時機 | 下載后立即執行 | 下載完成后立即執行 | HTML 解析完成后執行 |
執行順序 | 按標簽順序 | 不確定(下載完成順序) | 按標簽順序 |
DOM 依賴 | 需確保 DOM 已加載 | 不依賴 DOM | 可依賴 DOM |
最佳實踐
- 優先使用
defer
:對于需要操作 DOM 或依賴于其他腳本的代碼,使用defer
。 - 使用
async
加載獨立腳本:對于不依賴其他資源的異步腳本(如第三方庫),使用async
。 - 內聯腳本避免使用
async
/defer
:async
和defer
僅對外部腳本(src
屬性)有效,對內聯腳本無效。
兼容性
async
和defer
均支持現代瀏覽器及 IE10+。- 若需兼容 IE9 及以下版本,需使用默認加載方式或通過 JavaScript 動態加載腳本。