async脚本执行时机不可控,下载完成即中断dom解析执行;defer脚本则在html解析完毕后、domcontentloaded前按序执行,确保dom就绪。

async 脚本执行时机不可控,可能中断 DOM 解析
async 脚本一旦下载完成,浏览器会立刻暂停 HTML 解析去执行它——哪怕此时 都还没解析到。这意味着:如果脚本里调用 document.getElementById("app"),大概率返回 null;如果多个 async 脚本有依赖(比如 lodash.js 和用到它的 utils.js),utils.js 可能先执行、报 ReferenceError: _ is not defined。
常见错误现象包括:Cannot read property 'addEventListener' of null、Vue 应用挂载失败、首屏内容渲染被意外打断。
- 适用场景仅限完全独立的脚本:如
analytics.js、error-tracking.js、第三方广告加载器 - 多个
<script async src="a.js"></script>和<script async src="b.js"></script>之间无执行顺序保证 - 即使脚本体积小,也不能假设它“一定早于 DOM 构建完成时执行”——执行时机只取决于下载完成时刻
defer 脚本确保 DOM 就绪且按序执行
defer 的核心价值是:所有带 defer 的外链脚本,一定会在 DOMContentLoaded 事件触发前、整个 HTML 解析完毕后,按它们在文档中出现的顺序依次执行。此时 document.body、document.head、所有元素节点都已就位。
典型错误是误以为 <script defer>init();</script> 有效——defer 对内联脚本无效,只对带 src 的外部脚本起作用。
- 适合初始化逻辑、DOM 操作、库 + 插件组合(如先
vue.js再app.js) - 即使
app.js体积大、下载慢,只要加了defer,它仍会在 DOM 解析完后才执行,不会导致白屏或null引用 - Webpack/Vite 输出的
type="module"脚本默认具备defer语义,再手动加defer属于冗余
async 和 defer 不能共存,浏览器只认 async
写成 <script async defer src="main.js"></script> 是无效配置。HTML 规范明确:当两者同时存在时,async 优先级更高,defer 被静默忽略。这不是 bug,是标准行为。
容易踩的坑是构建工具自动注入属性时未做排他判断,或者多人协作时有人加 async、有人加 defer,最终结果不可预期。
- 务必检查最终 HTML 中是否同时出现两个属性
- CI/CD 流程中可用正则扫描
<script>]*async[^>]*defer</script>或<script>]*defer[^>]*async</script>报警 - 同步脚本(无任何属性)仍是性能雷区,除非是极小的内联 polyfill(如检测
Promise是否存在)
真正影响执行时机的,是 script 标签位置 + 属性组合
很多人以为把 <script src="x.js"></script> 放到 前就万事大吉——其实不然。若没加 defer 或 async,它仍是同步阻塞的:浏览器解析到该标签时,会暂停 HTML 解析、下载并执行脚本,之后才继续。
更隐蔽的问题是:即使用了 defer,如果脚本内部动态 import() 其他模块,那些模块的加载和执行仍不受 defer 约束,得靠代码自己控制时机。
-
里的defer脚本和末尾的同步脚本,在实际执行时机上可能相差无几,但前者不阻塞渲染,后者会 - SSR 场景下,
同步脚本极易卡住首屏,必须显式声明defer或async - 移动端弱网环境下,async 脚本“谁先下完谁先跑”的特性可能导致关键业务逻辑被统计脚本抢占执行资源
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











