defer属性仅对外部脚本(含src)生效,内联脚本使用无效;多个defer脚本按HTML声明顺序执行;type="module"脚本天然defer,无需重复添加;defer脚本在DOMContentLoaded前执行完毕,但不保证其加载的资源就绪。

defer 属性只对外部脚本生效
内联脚本写 defer 是无效的,浏览器会直接忽略。比如:<script defer>console.log('hi')</script> 中的 defer 不起作用,脚本仍会阻塞解析、立即执行(如果在 里,大概率取不到 DOM 元素)。必须搭配 src 属性才真正触发 defer 行为:<script src="init.js" defer></script>。
多个 defer 脚本必须按顺序声明
执行顺序完全由 HTML 中标签出现的先后决定,不是由文件名、大小或网络响应快慢决定。常见错误包括:
- 把
vendor.js放在app.js后面却指望它先执行 - 构建工具自动重排 script 标签顺序(如某些 webpack 插件插入 polyfill 到最前),破坏了依赖链
- 用
document.write()动态注入 defer 脚本——该行为已被现代浏览器禁用,且defer在动态创建的<script></script>上兼容性差(尤其 IE 和部分旧版 Safari)
defer 不等于 DOMContentLoaded 事件监听器
两者时机接近但不等价:defer 脚本在 DOMContentLoaded 触发前执行完毕;而手动加的 document.addEventListener('DOMContentLoaded', ...) 回调,会在所有 defer 脚本执行完后才开始运行。这意味着:
- 如果你在
init.js(带 defer)里绑定了全局函数,又在<script>DOMContentLoaded</script>里调用它——没问题 - 但反过来:在
DOMContentLoaded回调里定义了一个函数,指望被 defer 脚本调用——会报ReferenceError -
defer脚本内部不能依赖DOMContentLoaded回调里的变量或副作用
type="module" 脚本天然 defer,别重复加
<script type="module" src="main.js"></script> 已隐含 defer 行为:并行下载、DOM 就绪后执行、按顺序执行。再加 defer 属于冗余,虽然多数浏览器能容错,但语义混乱,还可能干扰某些构建工具的模块分析逻辑。另外注意:type="module" 自带严格模式、顶层 await,和传统脚本行为有差异,迁移时要验证。
app.js(defer)里用 fetch('./model.bin'),脚本执行时 DOM 是 ready 的,但 model.bin 还在下载中——这时的“就绪”只是 DOM 层面,不是业务数据层面。











