| 特性 | 普通 script | defer | async |
|---|---|---|---|
| 下载阻塞 HTML 解析 | 是 | 否 | 否 |
| 执行时阻塞 HTML 解析 | 是 | 解析完成后执行 | 可能 |
| 保证执行顺序 | 是 | 是 | 否 |
| 典型用途 | 必须同步执行的脚本 | 业务主脚本 | 独立第三方脚本 |
理解普通 script、async、defer 的核心,就是区分 JavaScript 的「下载阶段」和「执行阶段」。
还需要关注一个关键问题:JS 在什么时机执行?
一、三者的区别
| 类型 | JS 下载阶段 | JS 执行时机 | 执行是否阻塞 HTML 解析 |
|---|---|---|---|
| 普通 script | 阻塞 HTML 解析 | 下载完成立即执行 | 是 |
| async | 不阻塞 HTML 解析 | 下载完成后尽快执行 | 可能 |
| defer | 不阻塞 HTML 解析 | HTML 解析完成后执行 | 否 |
注意:这里讨论的是经典外部脚本。
二、通过时间线理解
1. 普通 script
<script src="main.js"></script>
HTML 解析 ─────┐ ┌────── 继续解析
│ │
JS 下载 └───────┐ │
│ │
JS 执行 └─────────┘
遇到脚本后,HTML 解析暂停,等待 JS 下载并执行完成。
2. async
<script src="main.js" async></script>
HTML 解析 ─────────────────┐ ┌──── 继续解析
│ │
JS 下载 ─────────┐ │ │
│ │ │
JS 执行 └───────┴───────┘
JS 下载和 HTML 解析并行,但下载完成后,JS 执行可能打断 HTML 解析。
3. defer
<script src="main.js" defer></script>
HTML 解析 ──────────────────────┐
│
JS 下载 ──────────────┐ │
│ │
JS 执行 │ └─────────
│
等待 HTML 解析完成
JS 下载和 HTML 解析并行,等 HTML 解析完成后再执行。
三、还有一个特别容易混淆的地方
不阻塞 HTML 解析 ≠ 不阻塞页面渲染。
例如:
<script src="main.js" defer></script>
虽然 defer 不阻塞 HTML 解析,但如果 main.js 中存在:
while (true) {}
JS 执行时仍然会持续占用主线程,导致页面无法正常更新和响应交互。
因此,需要区分三个概念:
- 下载阻塞:下载 JS 时,是否暂停 HTML 解析。
- 执行阻塞:执行 JS 时,是否暂停 HTML 解析。
- 渲染阻塞:JS 执行是否妨碍浏览器完成页面绘制。
四、面试记忆口诀
普通 script:下载等,执行也等。
async:下载不等,下载完就执行。
defer:下载不等,HTML 解析完再执行。
最后补充一个细节:async 不保证多个脚本的执行顺序,而 defer 会按照它们在 HTML 中出现的顺序执行。
这也是两者除了执行时机之外的重要区别。