defer属性仅对外部脚本(含src)生效,内联脚本加defer会被浏览器忽略;其执行时机在DOM构建完毕后、DOMContentLoaded事件触发前,且多个defer脚本严格按HTML中声明顺序执行。

defer 属性只对外部脚本有效
如果你给 <script> 标签写了 defer,但没用 src 属性(也就是写的是内联脚本),那这个 defer 完全不会生效。浏览器直接忽略它——既不延迟执行,也不改变执行顺序。
常见错误是这样写:
<script defer>
console.log('我不会被延迟');
</script>
正确用法必须带 src:
<script src="app.js" defer></script>
-
defer只作用于有src的脚本 - 内联脚本想延迟执行,得自己用
DOMContentLoaded或setTimeout - 多个
defer脚本按出现顺序执行,这点和async不同
defer 和 async 的执行时机差异
两者都让脚本不阻塞 HTML 解析,但关键区别在「何时执行」和「是否保序」:
立即学习“前端免费学习笔记(深入)”;
-
async:下载完立刻执行,可能打断 DOM 构建;多个之间无序 -
defer:等整个 HTML 解析完、DOM 构建完成后再统一执行,严格按<script>标签顺序
比如页面里有:
<script src="a.js" defer></script> <script src="b.js" defer></script>
那么 a.js 一定在 b.js 之前执行,哪怕 b.js 下载更快。
而换成 async,谁先下完谁先跑,顺序无法保证。
defer 脚本能访问完整 DOM 吗?
能,但有个前提:它必须在 DOMContentLoaded 事件触发前执行完毕。实际中几乎总是满足——因为 defer 脚本的执行时机就是 DOM 构建完成后、DOMContentLoaded 触发前。
所以你可以放心写:
<script src="init.js" defer></script>
并在 init.js 里直接操作 document.getElementById 或 document.querySelector,不用加事件监听。
- 如果脚本体积很大、执行很慢,有可能拖到
DOMContentLoaded之后才执行完,但 DOM 已存在,只是事件可能已触发 - 不推荐在
defer脚本里依赖DOMContentLoaded事件本身——它已经过了 - 若需确保在事件触发后运行,还是显式监听更稳妥
兼容性和 polyfill 注意点
defer 在所有现代浏览器中都支持,包括 IE10+。但 IE9 及更早版本会忽略它,把脚本当普通同步脚本加载——即阻塞解析、立即执行。
如果你必须支持 IE9,不能靠 defer 实现非阻塞,得手动把脚本动态插入或用 document.write(不推荐)。
- 检查是否生效很简单:打开 DevTools → Network → 查看脚本请求是否和 HTML 并行下载
- Chrome/Firefox 的 Elements 面板里,
defer脚本的async和defer属性会显示为灰色(表示被识别但不用于内联) - 注意:Webpack/Vite 等构建工具生成的
<script>标签默认不加defer,得手动配置或在模板里写
defer 不是“延迟几毫秒”,而是绑定在 HTML 解析流程里的确定阶段;一旦你混用 async 和 defer,或者误以为内联脚本能受控延迟,行为就会偏离预期。



















