defer 和 async 的区别
1. 先记住结论
defer 和 async 都可以让外部脚本在下载时不阻塞 HTML 解析,但它们的执行时机不同:
defer:等 HTML 解析完成后,按照脚本在文档中的顺序执行。async:脚本下载完成后尽快执行,不保证执行顺序。
对于 defer,更准确的执行条件是:
HTML 已经解析完成、脚本已经下载完成,并且已经轮到该脚本按文档顺序执行。
因此:
- 脚本先下载完:等待 HTML 解析完成。
- HTML 先解析完:等待脚本下载完成。
2. defer 的完整执行时机
也就是:
defer 脚本执行时,完整的 DOM 已经构建完成,所以可以直接访问页面中的 DOM 元素。
3. defer、async 和普通脚本对比
可以简单理解为:
- 普通脚本:遇到就停下来执行。
async:谁先下载完,谁先执行。defer:先解析完 HTML,再排队执行。
4. 多个 defer 脚本的执行顺序
即使 b.js、c.js 比 a.js 更早下载完成,执行顺序仍然是:
因此,当 b.js 依赖 a.js 时,可以使用 defer 保证依赖顺序。async 不适合这种场景。
5. load 会早于 defer 执行完吗?
正常情况下不会。
浏览器必须先执行完 defer 脚本,才会触发 DOMContentLoaded;而 window.load 又发生在 DOMContentLoaded 之后。因此,window.load 不可能在 defer 脚本的同步代码执行完之前触发。
但需要区分:
defer脚本本身执行完毕。defer脚本启动的异步任务执行完毕。
这里的 defer.js 已经执行完毕,只是它创建的定时器回调还没有执行。浏览器不会为了这个异步回调而推迟 DOMContentLoaded 或 window.load。
6. 适用场景
defer
适合普通业务脚本,尤其是:
- 需要操作完整 DOM。
- 脚本之间存在依赖关系。
- 希望脚本下载不阻塞 HTML 解析。
async
适合与页面其他代码没有依赖关系的独立脚本,例如:
- 数据统计。
- 广告脚本。
- 独立的第三方 SDK。
7. 常见易错点
defer 对内联脚本不起作用
defer 主要用于带有 src 的外部经典脚本,下面的 defer 不会产生推迟效果:
type="module" 默认具有类似 defer 的行为
模块脚本默认不会阻塞 HTML 解析,并且会在文档解析完成后执行:
通常不需要再为模块脚本添加 defer。如果添加 async,它会在依赖模块下载完成后尽快执行,不再等待文档解析完成。
8. 面试回答模板
defer脚本会与 HTML 并行下载,不阻塞 HTML 解析。它会等到 HTML 解析完成后、DOMContentLoaded触发之前,按照脚本在文档中的顺序执行。DOMContentLoaded会等待所有defer脚本执行完成,但不会等待这些脚本启动的定时器、网络请求等异步任务完成。

