defer脚本在HTML解析完成、DOMContentLoaded触发前按书写顺序执行,DOM一定可用;async脚本下载完立即中断解析执行,时机不可控且DOM可能未就绪。

两者都不阻塞 HTML 解析,但「不阻塞」只发生在下载阶段;一旦执行开始,都会中断解析——关键差异在于执行时机是否可控、是否等 DOM 就绪。
defer 脚本何时执行?DOM 一定可用吗?
defer 脚本的执行被浏览器硬性锁定在两个节点之间:HTML 字符串解析完成之后、DOMContentLoaded 事件触发之前。这意味着:
-
document.body、document.head和所有已声明的元素(哪怕写在</body>后面)都已存在,document.querySelector('#app')不会返回null - 多个
<script defer src="a.js"></script>和<script defer src="b.js"></script>严格按 HTML 中顺序执行,a.js一定先于b.js - 即使
b.js下载更快,它也必须等a.js执行完才轮到自己——执行是串行保序的,不是“下载完就一起跑”
async 脚本为什么常导致 document.getElementById 返回 null?
async 脚本一旦下载完成,浏览器会立刻暂停 HTML 解析去执行它。此时 DOM 树大概率没建完:
- 如果脚本出现在
<head>里,执行时<body>可能还没开始解析,document.body是undefined - 即使脚本放在
</body>前,也无法保证它比同页面其他 async 脚本慢——谁先下完谁先执行,顺序完全不可控 -
console.log(document.getElementById('main'))输出null的根本原因,不是脚本“加载太早”,而是执行时机脱离了 DOM 构建节奏
内联脚本加 defer 或 async 有效吗?
完全无效。这两个属性只对带 src 的外部脚本生效:
立即学习“前端免费学习笔记(深入)”;
-
<script defer>init();</script>→defer被静默忽略,init()立即执行,可能报ReferenceError -
<script async>console.log('hi');</script>→ 同样被忽略,直接输出 - 服务端渲染(SSR)中常见错误:把初始化逻辑写在
<head>内联脚本里,以为加个defer就能延迟——其实毫无作用,必须拆成src外部引用
async 和 defer 能不能同时写?
可以写,但浏览器只认 async 行为,defer 被忽略。HTML 规范明确:当两个属性共存时,async 优先级更高。
真正容易被忽略的点是:你改了一个 async 为 defer,却没检查它是否还依赖另一个没改的 async 脚本——比如 jQuery 加了 async,而插件脚本加了 defer,结果插件执行时 $ 还没定义。这种混合用法比全错更难排查。



















