defer脚本在DOM解析完成、DOMContentLoaded触发前按HTML顺序执行,可安全访问全部DOM元素;async脚本下载完立即中断解析执行,执行时机不可控且不保序,易导致document.querySelector返回null。

defer 和 async 都不阻塞 HTML 解析,但执行时机、顺序保障和 DOM 可用性完全不同——选错一个,document.querySelector('#app') 就可能返回 null。
defer 脚本一定等 DOM 解析完才执行
浏览器遇到 <script defer src="app.js"></script> 时,会立刻发起下载,但把执行“锁死”在两个节点之间:HTML 解析完成之后、DOMContentLoaded 触发之前。
- 所有
defer脚本都能安全访问document.body、document.head和任意已声明的 DOM 元素 - 多个
defer脚本严格按 HTML 中的书写顺序执行(vue.js在app.js前面,就一定先执行) - 即使某个脚本下载慢,也不会拖慢其他
defer脚本的执行时机——它们统一等到解析结束再批量跑 -
defer对内联脚本无效:<script defer>init();</script>中的init()会立刻运行,属性被忽略
async 脚本下载完就中断解析执行
<script async src="analytics.js"></script> 的行为是“能多快就多快”:下载并行、不阻塞解析,但一旦下载完成,浏览器会立刻暂停 HTML 解析,执行该脚本。
- 执行可能发生在
<body>还没开始解析、甚至<head>都没闭合的时候 - 多个
async脚本谁先下载完谁先执行,完全无序;ads.js比analytics.js小,就可能先跑 - 如果脚本里调用
document.getElementById('main'),而对应元素还没解析到,结果就是null -
async和defer不能共存,浏览器只认async行为,另一个被忽略
两者都只对带 src 的外部脚本生效
这是最容易被忽略的硬约束。无论是 defer 还是 async,只要没有 src 属性,就完全无效。
立即学习“前端免费学习笔记(深入)”;
-
<script defer>console.log(1)</script>→defer被静默忽略,console.log(1)立即执行 - 服务端渲染(SSR)中,常有人把初始化逻辑写在
<head>的同步脚本里,误以为加个defer就能延迟——其实它根本没作用,必须换成外部引用 - 想动态控制加载时机(比如用户点击后才加载地图 SDK),还是用
document.createElement('script')更可靠,不受 HTML 解析阶段限制
常见错误现象与排查线索
页面初始化失败、DOM 操作报错、第三方库未定义——这些问题八成出在 async/defer 误配。
-
TypeError: Cannot read property 'addEventListener' of null→ 脚本用了async却尝试绑定 DOM 元素,应换defer或加DOMContentLoaded包裹 -
ReferenceError: $ is not defined→jQuery加了async,而后续插件脚本没加或加了但执行更早,应统一改用defer - 埋点数据漏发或重复上报 → 统计脚本用了
defer导致太晚触发,可改async;若需确保 DOM 稳定后再上报,保留defer更稳妥 - Chrome DevTools Network 面板中,
async脚本的Initiator显示为parser,但执行时机在下载完成瞬间,这点容易误判为“已就绪”



















