Script 标签中 defer 与 async 的区别
面试速答(30 秒版 TL;DR)
defer和async都能让脚本下载时 不阻塞 HTML 解析,这是它们相对普通同步脚本的共同点。- 核心区别在 执行时机和顺序保证:
async:下载完就立刻执行,执行时会中断 HTML 解析;多个async脚本执行顺序不保证。defer:等 HTML 全部解析完成后再按文档顺序执行;适合主业务脚本。
- 高频结论:
- 有依赖关系、需要稳定顺序:用
defer - 独立脚本,如统计、埋点、广告:用
async
- 有依赖关系、需要稳定顺序:用
先看三种脚本的行为差异
| 脚本类型 | 下载是否阻塞 HTML 解析 | 执行时机 | 是否保证顺序 |
|---|---|---|---|
普通 <script> | 会 | 下载完立即执行 | 按出现顺序 |
<script async> | 不会 | 下载完立即执行 | 不保证 |
<script defer> | 不会 | HTML 解析完成后执行 | 保证 |
时间线是最重要的理解方式
1)普通同步脚本为什么慢?
<script src="/app.js"></script>
浏览器遇到它时通常要做三件事:
- 暂停 HTML 解析
- 下载脚本
- 执行脚本
执行完后才继续往下解析页面。
所以同步脚本特别容易拖慢首屏和 DOM 构建。
2)async 的特点
<script async src="/analytics.js"></script>
<script async src="/ads.js"></script>
特点:
- 下载过程不阻塞 HTML 解析
- 谁先下载完谁先执行
- 执行那一刻会暂停 HTML 解析
- 多个 async 脚本之间 没有顺序保证
适合:
- 埋点
- 广告
- 独立统计脚本
- 不依赖 DOM 完整结构、也不依赖其他脚本顺序的资源
不适合:
- 有前后依赖关系的业务脚本
- 必须等 DOM 完整后再执行的初始化逻辑
3)defer 的特点
<script defer src="/vendor.js"></script>
<script defer src="/app.js"></script>
特点:
- 下载过程不阻塞 HTML 解析
- 要等 HTML 解析完成后再执行
- 多个 defer 脚本 按文档顺序执行
- 一般在
DOMContentLoaded之前执行完
适合:
- 主业务入口脚本
- 有先后依赖关系的脚本
- 需要操作完整 DOM 的初始化逻辑
高频考点:DOMContentLoaded 和它们的关系
你可以这样记:
defer脚本会在 HTML 解析结束后、DOMContentLoaded触发前执行async脚本不保证和DOMContentLoaded的相对顺序
所以如果面试官问“为什么很多主入口脚本喜欢用 defer”,一个高质量回答是:
因为它既避免了下载阶段阻塞 HTML 解析,又能保证脚本按顺序在 DOM 构建完成后执行,和页面初始化流程更匹配。
典型选择场景
场景 1:主业务脚本
<script defer src="/runtime.js"></script>
<script defer src="/vendor.js"></script>
<script defer src="/main.js"></script>
理由:
- 依赖顺序稳定
- 不阻塞 HTML 解析
场景 2:第三方统计脚本
<script async src="https://example.com/analytics.js"></script>
理由:
- 越早加载越好
- 不依赖其他业务脚本顺序
高频追问:defer 和把脚本放在 body 底部有什么区别?
相同点:
- 都能减少对前面 HTML 解析的阻塞感
不同点:
defer脚本可以更早开始下载,因为浏览器在解析到标签时就会并行拉取- 把脚本放在底部,往往是“解析快结束时才开始下载”
defer还能天然表达“这是延迟执行脚本”的语义
工程上现代项目通常更偏向 defer。
高频追问:模块脚本呢?
<script type="module" src="/main.js"></script>
面试里一个常见补充点是:
type="module"的脚本在行为上通常更接近延迟执行,不会像传统同步脚本那样阻塞整个 HTML 解析流程- 但它还有模块解析、依赖图加载等额外语义,不能简单等同于普通
defer
如果面试官没追问,这一层点到为止就够了。
典型题 & 标准答法
Q1:async 和 defer 的区别是什么?
答法建议:
- 共同点:下载都不阻塞 HTML 解析
- 区别:
async下载完立即执行、顺序不保证;defer等 HTML 解析完再按顺序执行
Q2:主业务脚本为什么一般用 defer?
答法建议:
- 因为主业务脚本通常有依赖顺序,还要操作完整 DOM
defer同时满足“并行下载”和“顺序执行”
Q3:埋点脚本为什么常用 async?
答法建议:
- 因为它通常独立,不依赖其它脚本
- 越早下载执行越好,没必要等完整 DOM
常见追问
async会不会阻塞解析?- 下载阶段不阻塞,但执行阶段会暂停 HTML 解析。
defer一定在DOMContentLoaded前执行吗?- 经典结论是这样理解最稳,面试里这么答通常足够。
- 能不能同时写
async defer?- 在经典脚本语义里通常以
async行为为主,工程上不要这么写,避免歧义。
- 在经典脚本语义里通常以
易错点 / 坑
- 把“下载不阻塞”误解成“执行也不阻塞”。
- 用
async加载有依赖顺序的脚本,导致偶发报错。 - 把
defer和“脚本永远最后执行”画等号,忽略它仍属于页面初始化链路的一部分。 - 把模块脚本简单粗暴等同于普通
defer。
速记要点(可背诵)
async:独立脚本,下载完就执行,顺序不保证。defer:业务脚本,HTML 解析完再执行,顺序保证。- 共同点是下载不阻塞解析,不同点是执行时机和顺序。