非常好的问题 👍
这是前端性能优化和脚本加载机制里非常关键的点。
我们来彻底讲清楚——<script> 的 defer 和 async 的区别、执行顺序、应用场景👇
🧩 一、基础:浏览器加载 <script> 时发生了什么?
默认情况下,浏览器在加载 HTML 时遇到 <script> 会:
- 暂停 HTML 解析
- 开始下载脚本
- 下载完成后立即执行
- 执行完后再继续解析 HTML
所以,普通 script 会阻塞页面渲染👇
html
<!-- 会阻塞 HTML 解析 -->
<script src="app.js"></script>
<p>这段文字要等上面脚本执行完才能渲染</p>
⚡ 二、async 和 defer 的出现:解决阻塞问题
✅ 1️⃣ async(异步加载、独立执行)
html
<script src="a.js" async></script>
<script src="b.js" async></script>
特点:
| 特性 | 说明 |
|---|---|
| 加载方式 | 异步下载,不阻塞 HTML |
| 执行时机 | 下载完成后立即执行(哪一个先下完先执行) |
| 执行顺序 | 不确定!(谁先下载完谁先执行) |
| 是否等待 DOM | ❌ 不等待 |
| 适用场景 | 独立、不依赖其他脚本(如统计、广告) |
🧠 举例:
html
<!-- 这两个脚本会同时下载,但执行顺序随机 -->
<script src="track.js" async></script>
<script src="ads.js" async></script>
✅ 2️⃣ defer(延迟执行、按顺序执行)
html
<script src="a.js" defer></script>
<script src="b.js" defer></script>
特点:
| 特性 | 说明 |
|---|---|
| 加载方式 | 异步下载,不阻塞 HTML |
| 执行时机 | HTML 解析完成后(DOMContentLoaded 前)执行 |
| 执行顺序 | ✅ 按照在 HTML 中的顺序依次执行 |
| 是否等待 DOM | ✅ 会等待 DOM 解析完成 |
| 适用场景 | 脚本依赖 DOM、依赖其他脚本(例如框架、初始化逻辑) |
🧠 举例:
html
<script src="react.js" defer></script>
<script src="main.js" defer></script>
👉 浏览器会:
- 同时下载
react.js和main.js - 等 HTML 解析完
- 按顺序执行 react → main
🧮 三、执行时序对比图
| 顺序 | 普通 <script> |
<script async> |
<script defer> |
|---|---|---|---|
| 下载 | ⏸️ 阻塞下载 | ⏩ 异步下载 | ⏩ 异步下载 |
| 执行时机 | 下载完立刻执行 | 下载完立刻执行 | HTML 解析完后执行 |
| 执行顺序 | 按出现顺序 | 不确定 | 按出现顺序 |
| 是否阻塞 HTML | ✅ 阻塞 | ❌ 不阻塞 | ❌ 不阻塞 |
| 是否等待 DOM | ❌ 不等待 | ❌ 不等待 | ✅ 等待 |
🧠 四、使用建议总结
| 场景 | 推荐属性 | 理由 |
|---|---|---|
| 独立脚本(分析、广告、SDK) | async |
不依赖 DOM 和其他脚本 |
| 依赖 DOM 的脚本(主逻辑、框架) | defer |
DOM 解析完成后执行,顺序可控 |
| 需要立刻执行(初始化前置) | 无 | 主线程阻塞执行,保证顺序 |
🧩 五、实践案例:性能优化推荐写法
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>脚本加载优化示例</title>
<!-- 框架依赖:延迟执行但保证顺序 -->
<script src="react.js" defer></script>
<script src="app.js" defer></script>
<!-- 第三方统计:异步执行即可 -->
<script src="analytics.js" async></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
💡 六、额外说明:模块化脚本(type="module")
如果是现代项目(例如 React、Next.js、Vue3),你会看到:
html
<script type="module" src="main.js"></script>
这时:
type="module"的脚本默认就是 defer 的行为- 不需要再加
defer - 可使用
import/export模块语法
👉 相当于:
html
<script type="module" src="main.js" defer></script>
✅ 七、总结一句话记忆
| 属性 | 行为总结 |
|---|---|
| 无 | 阻塞下载 + 阻塞执行(同步) |
| async | 异步下载 + 下载完立即执行(无序) |
| defer | 异步下载 + DOM 解析后按顺序执行 |
是否要我帮你画一个「script 加载与执行时序图」?
那样你会更直观地看到 async、defer 和普通加载的差别。
