defer 是同步执行但延迟执行的脚本加载方式,它并行下载外部脚本并在 dom 构建完成后按序执行,不适用于内联脚本,执行时机在 domcontentloaded 之前,且需注意兼容性与常见失效场景。

defer 是同步加载,不是异步
很多人误以为 defer 是“异步加载”,其实它既不阻塞 HTML 解析,也不异步下载——它是**并行下载 + 同步执行(但延迟)**。浏览器会按顺序下载脚本,等整个 HTML 解析完成、DOM 构建完毕后,再按 <script></script> 出现顺序依次执行。这点和 async 有本质区别:后者是真正异步,下载完立刻执行,不保证顺序。
defer 必须用在没有 src 的内联脚本上吗?
不能。defer 只对带 src 的外部脚本有效,对内联脚本(即没有 src 的 <script>console.log(1)</script>)加 defer 会被忽略。浏览器直接执行它,无视该属性。
- ✅ 正确用法:
<script src="a.js" defer></script> - ❌ 无效写法:
<script defer>console.log('hi')</script> - ⚠️ 注意:多个
defer脚本仍严格按 HTML 中的书写顺序执行,哪怕b.js比a.js先下载完
defer 和 DOMContentLoaded 的关系
defer 脚本的执行时机,恰好卡在 DOMContentLoaded 事件触发之前——也就是说,所有 defer 脚本执行完,才会派发该事件。如果你在 DOMContentLoaded 回调里操作 DOM,而依赖某个 defer 脚本定义的函数或变量,那没问题;但反过来,如果在 defer 脚本里监听 DOMContentLoaded,它可能已经触发过了(尤其当脚本体积小、加载快时),导致监听失效。
- 推荐做法:直接写逻辑,别等事件——
defer脚本天然能访问完整 DOM - 避免写:
document.addEventListener('DOMContentLoaded', init)在defer脚本里 - 若必须统一入口,改用
document.readyState === 'interactive'或直接执行
兼容性和常见失效场景
defer 在所有现代浏览器中都支持(IE9+),但它很容易被“无意破坏”:
- 放在
中间?可以,但必须在解析到它时,HTML 还没结束(即不能在之后) - 动态插入的
<script defer src="x.js"></script>不会生效——defer只对初始 HTML 解析阶段的脚本起作用 - 和
type="module"同时出现?模块脚本默认行为就类似defer,此时再加defer属于冗余,部分浏览器会忽略或报 warning - 服务器返回的 JS 响应头含
Content-Disposition: attachment?会中断 defer 流程,脚本不执行
defer 当成“让脚本晚点加载”的开关,结果发现首屏渲染没变快——因为下载本身并不延迟,只是执行时机可控。真要降低初始负载,得配合 code splitting 或动态 import()。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











