defer 脚本不会在 HTML 解析完成前执行,它严格等待 DOM 树构建完毕、document.readyState 变为 'interactive' 末尾时才运行,即便脚本已下载完成也会挂起,且不保证动态插入内容就绪。

会不执行。defer 脚本的执行被明确推迟到 HTML 文档解析完成之后、DOMContentLoaded 事件触发之前,它绝不会在文档完全解析前运行。
defer 的执行时机由浏览器严格保证
浏览器遇到 <script src="app.js" defer></script> 时,会立即发起下载,但完全不执行,直到整个 HTML 字节流被解析完毕、DOM 树构建完成。这个“完成”指所有起始标签、结束标签、文本节点都已处理完毕,document.readyState === 'interactive' 阶段末尾。
- 哪怕脚本已提前下载完,也会被挂起等待解析结束
- 哪怕 DOM 中只有一个
<div id="app"></div>,defer 脚本也必须等它被插入 DOM 后才执行 - 如果页面含大量嵌套标签或模板字符串,defer 不会“抢跑”,它只认解析器的最终状态
常见误判:为什么有时看起来“提前执行”了?
这不是 defer 失效,而是你观察的位置或方式出了问题:
-
console.log输出位置误导:脚本里写console.log('start'),但控制台时间戳受 DevTools 渲染延迟影响,容易误判为“刚加载就执行” - 混用了
async:若同一页面有<script src="a.js" async></script>,它可能早于 defer 脚本执行,造成“defer 慢”的错觉 - 脚本本身没做 DOM 操作:比如只调用
Math.random(),没访问任何元素,就无法验证是否真等到了 DOM 就绪 - 服务端渲染(SSR)注入的初始状态被误当作 defer 执行结果:window 变量可能早在解析阶段就被 script 注入,和 defer 无关
怎么验证 defer 确实没在解析前执行?
最直接的方式是主动探测 DOM 状态:
立即学习“前端免费学习笔记(深入)”;
if (document.getElementById('app') === null) {
throw new Error('defer executed before DOM ready — impossible');
}
把这个逻辑放进你的 app.js,再配合 Chrome 的 Performance 面板看 Parse HTML 阶段结束时间点,就能确认 defer 是否守约。注意:document.querySelector('body') 在 defer 脚本中一定非空,但 document.body.innerHTML 可能还不包含后续动态插入的内容——这是正常现象,defer 只保证静态 DOM 就绪,不等待 JS 动态操作。
真正容易被忽略的是:defer 不解决资源加载阻塞(比如图片、CSS),它只管自己那行 <script></script> 的执行时机;而且一旦构建工具把外部脚本内联成 <script>...</script>,你写的 defer 就彻底消失,连静默退化都察觉不到。



















