defer属性仅对外部脚本有效,要求有src且非type="module";其执行时机在DOM构建完成但DOMContentLoaded事件触发前,按HTML顺序执行且不阻塞解析。

defer 属性只对外部脚本有效
如果你写了 <script defer src="app.js"></script>,它会等到 HTML 解析完成、DOM 构建完毕后再执行;但如果你写的是内联脚本,比如 <script defer>console.log(document.body);</script>,defer 会被浏览器完全忽略——这个行为在所有主流浏览器中一致。
所以第一步得确认:脚本必须带 src 属性,且不能有 type="module"(模块脚本默认具有类似 defer 的行为,再加 defer 也没用,但也不会报错)。
defer 脚本的执行时机比 DOMContentLoaded 还早一点
defer 脚本会在 HTML 解析结束、DOM 树构建完成之后立即执行,**但此时可能还没触发 DOMContentLoaded 事件**。这是因为该事件还要等所有 defer 脚本执行完才派发。
这意味着你可以在 defer 脚本里安全访问 document.body、遍历 document.querySelectorAll('button'),甚至绑定事件——不需要等 DOMContentLoaded 或 window.onload。
立即学习“前端免费学习笔记(深入)”;
常见误操作:
- 在
defer脚本里又去监听DOMContentLoaded,纯属多此一举 - 以为
defer等同于document.addEventListener('DOMContentLoaded', ...),其实前者更早、更确定
多个 defer 脚本按顺序执行,且不阻塞解析
浏览器会按 <script defer> 在 HTML 中出现的顺序下载并执行它们,即使后一个脚本体积小、先下载完,也必须等前一个执行完才轮到它。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
这对依赖关系很友好,比如:
<script defer src="lodash.js"></script><br><script defer src="utils.js"></script><br><script defer src="main.js"></script>
你不用在 main.js 里手动检查 lodash 是否加载完成——只要都加了 defer,顺序就天然保证。
注意点:
- 如果某个
defer脚本执行出错(比如语法错误或运行时报ReferenceError),后续的defer脚本仍会继续执行(不会中断队列) -
defer不影响脚本下载优先级,浏览器仍按常规策略并发下载,只是执行被推迟
和 async 对比时最容易踩的坑
async 是“下载完立刻执行”,不保证顺序,也不等 DOM;defer 是“等 DOM 解析完,再按顺序执行”。两者互斥,别同时写。
典型翻车场景:
- 把第三方统计脚本(如 GA、神策)设成
defer:可能 DOM 已就绪,但用户交互(比如点击按钮)发生在脚本执行前,导致漏埋点 - 在
<head>里放defer脚本,又在<body>底部放一个没defer的内联脚本:后者会立刻执行,大概率拿不到完整 DOM - 用构建工具自动注入脚本时,忘了检查是否已带
defer,结果重复添加导致行为不可控
真正需要延迟执行又依赖 DOM 的逻辑,defer 是最轻量、最可靠的选择;但如果你的脚本本身不操作 DOM(比如初始化 Web Worker 或预加载字体),async 反而更合适。
别低估 HTML 解析器对 defer 的处理精度——它不是“大概等一会儿”,而是精确卡在 DOM 树生成完毕、但尚未进入交互阶段的那个瞬间。这个边界稍不注意,就会在低网速或高 CPU 占用设备上暴露竞态问题。


















