async脚本下载完立即执行,可能dom未就绪;defer脚本等dom解析完成后再按序执行,确保dom就绪。两者均不阻塞html解析,但执行时机和顺序保障截然不同。

async 脚本执行时 DOM 可能还没解析完
浏览器遇到 <script src="a.js" async></script>,会立刻发起下载,但一旦下载完成,不管此时 解析到哪一行,都会中断 HTML 解析、立即执行。这意味着你在脚本里调用 document.getElementById('main') 很可能返回 null,因为对应元素还没被创建出来。
常见错误现象包括:
Cannot read property 'addEventListener' of null- 页面部分功能“偶尔失效”,尤其在弱网或脚本体积较大时更明显
- 多个
async脚本之间互相调用失败(比如utils.js比main.js晚下载完,但main.js已开始执行)
适用场景非常窄:只适合完全不操作 DOM、不依赖其他 JS、也不被其他 JS 依赖的代码,比如 Google Analytics 的 gtag() 初始化、某些广告 SDK 的加载器。
defer 脚本一定等 DOM 构建完才执行
defer 的关键保障是:所有带 defer 的脚本,一定会在 DOMContentLoaded 事件触发前、按 HTML 中书写顺序依次执行。此时整个 DOM 树已就绪,querySelector、addEventListener 全部可用。
但要注意几个硬限制:
- 只对带
src的外部脚本生效;<script defer>console.log('hi')</script>中的defer会被忽略 - 如果页面里混用
defer和普通同步脚本,后者仍会阻塞解析,破坏整体节奏 - 多个
defer脚本之间有强顺序依赖时(如先 jQuery 后插件),必须严格按依赖顺序写在 HTML 里
典型使用场景:页面初始化逻辑、表单验证库、带 DOM 绑定的 UI 组件加载。
async 和 defer 都不阻塞 HTML 解析,但行为完全不同
这是最容易混淆的点——光看“都不阻塞解析”就以为可以随便换,其实执行时机和顺序保障天差地别:
-
async:下载不阻塞,执行**随时打断**解析(可能在 就执行了) -
defer:下载不阻塞,执行**强制等到最后**(DOM 构建完、DOMContentLoaded前) - 两者都**不保证**与内联脚本(
<script>...</script>)的执行顺序
性能影响也不同:async 可能因过早执行而触发重排/重绘;defer 把执行集中到解析末尾,更利于浏览器做渲染优化。
怎么选?看脚本是否碰 DOM 或依赖其他脚本
直接按这个判断链走:
- 脚本需要访问
document或绑定事件?→ 必须用defer - 脚本要等另一个脚本先加载(比如插件要等框架)?→ 必须用
defer,且顺序不能错 - 脚本完全独立,只发个请求、埋个点、不读不写 DOM?→ 可用
async - 脚本是内联的(没
src)?→defer和async都无效,改外链或用DOMContentLoaded手动控制
现代项目中,90% 的业务脚本都应该用 defer;把 async 当作特例来处理,而不是默认选项。一个常被忽略的细节是:哪怕你只加了一个 async 脚本,它也可能因为下载快、执行早,抢在 defer 脚本之前运行并报错——这种跨脚本干扰很难复现,但线上真实存在。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











