不会。defer脚本绝不会在html解析完成前执行,其执行被严格限定在html解析结束、dom树构建完毕后且domcontentloaded事件触发前,此时所有静态元素已挂载,可安全访问dom。

defer脚本会不会在HTML解析完成前执行?
不会。defer脚本**绝不会**在HTML解析完成前执行——这是它的核心契约,也是它和async的根本区别。
为什么document.getElementById能直接用,但async里常报null?
因为defer的执行时机被浏览器严格限定在「HTML解析结束、DOM树构建完毕后,DOMContentLoaded事件触发前」这个窗口。此时所有静态元素(比如<div id="app">)已挂载进DOM,<code>document.getElementById("app")必然返回有效节点。
而async脚本一旦下载完成就立刻执行,可能发生在HTML解析中途——#app标签还没被读到,自然取不到。
- ✅ defer脚本里写
document.getElementById("root")是安全的,无需包裹DOMContentLoaded监听 - ❌ async脚本里必须自己判断:
if (document.getElementById("root")) { ... }或监听DOMContentLoaded - ⚠️ 注意:DOM就绪 ≠ CSSOM就绪,
getComputedStyle(document.body)在defer脚本里仍可能拿不到最终样式
哪些写法看似用了defer,其实根本没生效?
浏览器对defer的识别非常苛刻,稍有偏差就静默退回到同步行为——既不报错,也不提示,线上很难察觉。
- 内联脚本加
defer:<script defer>console.log("hi")</script>→ 直接忽略,立即执行 -
defer带值:<script src="a.js" defer></script>或<script src="a.js" defer></script>→ 只认无值布尔属性,带值即失效 - 混用
async:<script src="a.js" async defer></script>→ defer被完全忽略,按async规则执行 - 构建工具内联化:Vite开启
build.inlineDynamicImports: true时,外部脚本被转成内联 →defer彻底消失 - 写了
document.write()的脚本 → 浏览器主动禁用defer(该API本身已废弃)
如何验证defer是否真的按预期工作?
最简单可靠的方式,是在脚本开头加一句:console.log(document.readyState)。如果看到输出"interactive"(不是"loading",也不是"complete"),说明它确实在DOM就绪后、DOMContentLoaded前执行了。
别用document.body.children.length > 0来判断——空body也合法,这个检查不可靠。
真正容易被忽略的是:defer只约束“时机窗口”,不保证毫秒级精确时间点。网络延迟、JS主线程繁忙、甚至前面一个内联脚本清空了document.body,都可能让后续defer脚本看到意外的DOM状态。











