关于脚本执行的顺序
async 和 defer 都是用于优化脚本加载行为的 HTML 属性,它们都能解决脚本阻塞页面渲染的问题,但执行机制有本质区别:
一、async(异步加载)
<script async src="script.js"></script>
含义:
- 异步加载:立即开始下载脚本,不阻塞 HTML 解析
- 执行时机:脚本下载完成后立即执行(无论 HTML 是否解析完成)
- 执行顺序:多个
async脚本不保证执行顺序(先下载完先执行)
执行流程图:
HTML 解析开始
↓
遇到 async 脚本 → 启动下载(后台进行)
↓
继续解析 HTML
↓
脚本下载完成 → 立即暂停 HTML 解析 → 执行脚本
↓
脚本执行完毕 → 继续解析 HTML
适用场景:
- 完全独立的第三方脚本(如分析统计、广告脚本)
- 不依赖 DOM 或其他脚本的代码
- 对执行顺序无要求的场景
<!-- 示例:Google Analytics -->
<script async src="https://www.google-analytics.com/analytics.js"></script>
二、defer(延迟执行)
<script defer src="script.js"></script>
含义:
- 延迟执行:立即开始下载脚本,不阻塞 HTML 解析
- 执行时机:脚本在 HTML 解析完成后、DOMContentLoaded 事件前执行
- 执行顺序:多个
defer脚本严格按文档顺序执行
执行流程图:
HTML 解析开始
↓
遇到 defer 脚本 → 启动下载(后台进行)
↓
继续解析 HTML
↓ ↙ 其他 defer 脚本并行下载
完成 HTML 解析
↓
按顺序执行所有 defer 脚本
↓
触发 DOMContentLoaded
适用场景:
- 需要操作 DOM 的脚本
- 多个有依赖关系的脚本
- 需要等待 DOM 就绪的初始化代码
<!-- 示例:有依赖关系的库 -->
<script defer src="jquery.js"></script>
<script defer src="jquery-plugin.js"></script>
<script defer src="main.js"></script> <!-- 依赖前两个 -->
三、对比总结
| 特性 | async |
defer |
普通脚本 |
|---|---|---|---|
| 加载是否阻塞解析 | 不阻塞 | 不阻塞 | 阻塞 |
| 执行时机 | 下载完立即执行 | HTML解析后执行 | 下载完立即执行 |
| 执行顺序 | 无序(先下载完先执行) | 严格按文档顺序 | 按文档顺序 |
| DOMContentLoaded | 可能在其前/后执行 | 一定在其前执行 | 一定在其前执行 |
| DOM 依赖 | 可能操作未解析的 DOM(风险) | 可安全操作完整 DOM | 可能操作未解析的 DOM(风险) |
| 典型用途 | 统计、广告、独立模块 | 主应用脚本、有依赖的库 | 小型内联脚本 |
四、特殊组合场景
1. 同时使用 async 和 defer
<script async defer src="script.js"></script>
- 现代浏览器会优先采用
async行为 - 兼容旧浏览器的回退方案(IE9 以下会忽略
async只认defer)
2. 模块脚本的默认行为
<script type="module" src="app.js"></script>
- 默认具有
defer行为(HTML解析后执行) - 添加
async会转为异步行为:<script type="module" async src="app.js">
五、实际应用建议
<!-- 优先使用 defer -->
<script defer src="main-app.js"></script>
<!-- 完全独立的脚本用 async -->
<script async src="analytics.js"></script>
<!-- 关键渲染路径脚本可内联 -->
<script>
// 内联关键初始化代码
</script>
<!-- 现代模块化方案 -->
<script type="module">
import App from './App.js'; // 默认 defer 行为
</script>
黄金法则:
- 需要操作 DOM → 用
defer- 完全独立脚本 → 用
async- 框架入口 → 用
type="module"(默认 defer)- 关键渲染路径 → 内联脚本

浙公网安备 33010602011771号