一、核心区别对比
| 特性 | defer | async |
|---|---|---|
| 加载时机 | 与 HTML 解析并行加载 | 与 HTML 解析并行加载 |
| 执行时机 | DOM 解析完成后,按顺序执行 | 脚本加载完成后立即执行(可能打断 HTML 解析) |
| 执行顺序 | 严格按脚本在文档中的顺序执行 | 不保证顺序,先加载完的先执行 |
| 阻塞情况 | 仅在 DOM 解析完成后执行时阻塞 | 加载完成后立即中断 HTML 解析执行 |
| 兼容性 | IE9+ 及现代浏览器 | IE10+ 及现代浏览器 |
二、执行流程演示(关键差异)
// 假设页面有以下脚本
<script src="a.js"></script> // 普通脚本:阻塞解析
<script async src="b.js"></script> // async:并行加载,加载完立即执行
<script defer src="c.js"></script> // defer:并行加载,DOM 解析完成后按顺序执行
<script defer src="d.js"></script> // defer:同上
// 执行顺序可能为:
// 1. 解析 HTML → 遇到 a.js → 阻塞解析,加载并执行 a.js
// 2. 继续解析 HTML → 并行加载 b.js、c.js、d.js
// 3. b.js 先加载完成 → 立即执行(中断 HTML 解析)
// 4. 继续解析 HTML → 完成 DOM 构建 → 按顺序执行 c.js → d.js → 触发 DOMContentLoaded
三、典型应用场景
-
defer 的最佳场景
- 需操作 DOM 的脚本(如 jQuery 插件)。
- 多个有依赖关系的脚本(如先加载库,再加载依赖库的脚本)。
<script defer src="https://cdn.tailwindcss.com"></script> <script defer src="custom-script.js"></script> <!-- 确保 Tailwind 先加载,再执行自定义脚本 --> -
async 的最佳场景
- 独立功能脚本(如统计代码、广告脚本)。
- 不依赖 DOM 结构且不影响其他脚本的资源。
<script async src="https://www.googletagmanager.com/gtag/js"></script> <script async src="ad-script.js"></script> <!-- 无需关心执行顺序,加载完成即执行 -->
四、与普通脚本的性能对比
| 加载方式 | HTML 解析 | 阻塞情况 | 适用场景 |
|---|---|---|---|
| 无 defer/async | 遇到脚本立即加载并执行,阻塞 HTML 解析 | 强阻塞 | 必须立即执行的关键脚本 |
| defer | 并行加载,DOM 解析完成后执行 | 弱阻塞(仅执行阶段) | 需操作 DOM 且有依赖顺序的脚本 |
| async | 并行加载,加载完成立即执行 | 可能中断 HTML 解析 | 独立功能、无需顺序的脚本 |
五、问题
1. 问:defer 和 async 哪个更适合加载第三方库?
- 答:
- 若库有依赖关系(如 React → ReactDOM),用
defer保证顺序。 - 若无依赖(如 Google Analytics),用
async避免阻塞。
- 若库有依赖关系(如 React → ReactDOM),用
2. 问:defer/async 会影响 DOMContentLoaded 事件吗?
- 答:
defer脚本会在DOMContentLoaded之前执行(可能延迟该事件触发)。async脚本若在 DOM 解析完成前加载完成,会先执行脚本再触发事件。
3. 问:动态创建的脚本默认是 defer 还是 async?
- 答:
- 动态创建的脚本(如
document.createElement('script'))默认是async。 - 若需按顺序执行,需显式设置
script.defer = true。
- 动态创建的脚本(如