defer 脚本不阻塞 HTML 解析,因其执行被推迟到 DOM 构建完成后;它仅对带 src 的外部脚本生效,多个 defer 脚本按 HTML 书写顺序执行,且能安全操作 DOM,但不等待资源加载完成。

defer 不是用来“优化同步阻塞”的——它直接让脚本脱离同步执行流。只要用对,脚本就根本不会阻塞文档解析。
为什么 defer 脚本不阻塞 HTML 解析
浏览器遇到 <script src="app.js" defer></script> 时,会立即发起下载,同时继续往下解析 HTML,不暂停、不等待执行。这和没加任何属性的 <script src="app.js"></script> 有本质区别:后者会卡住解析,直到下载+执行完才继续。
- 阻塞解析的是“执行”,不是“下载”;
defer把执行推迟到 DOM 构建完成之后,所以解析全程畅通 - 即使
app.js下载慢、体积大、含长循环,HTML 解析也不受影响 - 但注意:
defer不改变下载优先级——它不会让脚本比 CSS 或关键图片更快下载
defer 必须搭配 src 才生效
这是最常踩的坑:<script defer>init();</script> 中的 defer 完全被忽略,脚本仍同步执行、阻塞解析。
- 只对外部脚本(带非空
src属性)有效;内联脚本加defer等同于没写 -
<script src="" defer></script>或<script src=" " defer></script>也被视为无src,退化为同步阻塞 - Vite 等构建工具可能把模块内联成
<script>...</script>,此时你手写的defer在最终 HTML 里已消失
多个 defer 脚本的执行顺序不能靠下载快慢保证
顺序只由它们在 HTML 中的书写位置决定。比如:
立即学习“前端免费学习笔记(深入)”;
<script src="lodash.js" defer></script> <script src="app.js" defer></script>
哪怕 app.js 先下载完,也一定等 lodash.js 执行完再跑——这是 defer 的核心契约。
- 顺序写反了就会报
ReferenceError: _ is not defined - 混用
async和defer(如<script src="a.js" async defer></script>)会导致defer被静默丢弃,按async规则执行,顺序失控 - 服务端注入的初始化数据(如
window.__INITIAL_STATE__)必须放在所有defer脚本之前,且自身不能加defer
defer 脚本能安全操作 DOM,但别指望资源加载完
它执行时机是 HTML 解析完成、DOM 树就绪后,早于 DOMContentLoaded 事件,所以你可以放心调用:
document.getElementById("header")document.querySelector("nav").addEventListener(...)document.body.appendChild(newEl)
但它不等图片、CSS、字体、fetch 请求或 <iframe> 加载完成——如果脚本里写了 img.naturalWidth 或依赖 load 事件,大概率拿到 0 或 null。
真正容易被忽略的是:路径 404、CSP 拦截、语法错误、模块 import() 失败……这些都不会被 defer 拦住或重试,它只管“什么时候执行”,不管“执行成不成”。



















