多个defer脚本的执行顺序严格按HTML中<script>标签的书写位置决定,与文件大小、网络快慢、下载完成时间无关;必须带src的外部脚本才生效,内联或无src写法无效,且不可与async混用。

defer 脚本的执行顺序到底由什么决定
多个 defer 脚本的执行顺序**只看 HTML 中 <script> 标签的书写位置**,和文件大小、网络快慢、下载完成时间完全无关。浏览器会等所有带 src 的 defer 脚本都下载完,再按 DOM 中出现的先后一个接一个执行。
常见错误是把依赖方写在前面:<script src="app.js" defer></script> 放在 <script src="utils.js" defer></script> 前面 → app.js 里调用 utils.doSomething() 就会报 ReferenceError: utils is not defined。
- 构建工具(如 Webpack/Vite)自动注入 polyfill 或 runtime 脚本到
<head>最前,可能无意中把utils.js挤到后面,破坏你手动排好的顺序 -
type="module"脚本和defer脚本不在同一执行队列里:混用时别让它们互相假设对方已执行完毕
哪些写法会让 defer 失效或行为退化
defer 只对带 src 属性的外部脚本生效,以下全部无效:
-
<script defer>init();</script>—— 内联脚本,defer被忽略,立即执行 -
<script src="" defer></script>或<script src=" " defer></script>——src为空或空格,等同于同步阻塞 -
<script src="a.js" defer="false"></script>——defer是布尔属性,赋值会被忽略,实际仍生效(但语义混乱)
真正起作用的只有两个硬条件:src 存在 + defer 出现(无值)。其余任何变体,浏览器都不会警告,只是默默退回到默认行为。
立即学习“前端免费学习笔记(深入)”;
async 和 defer 混用为什么等于埋雷
两者调度机制互不通信:async 脚本一下载完就插队执行,defer 脚本则老老实实排队等 DOM 解析结束。结果往往是依赖未定义、ReferenceError 频发。
典型错误写法:<script src="lib.js" defer></script> + <script src="init.js" async></script> → init.js 可能在 lib.js 下载前就执行了,或者 DOM 还没建好就去取元素。
- 修复方案:要么全用
defer(保序),要么全用async(仅限无依赖脚本),要么用document.createElement('script')动态插入来精确控制时机 - 动态插入时务必监听
onload和onerror,否则失败静默,极难调试
type="module" 和 defer 能不能一起用
不能,也不该加。type="module" 脚本天然具备 defer 行为:并行下载、DOM 就绪后执行、严格按声明顺序运行。再显式加 defer 属于冗余,部分构建工具(如 Webpack 的模块分析器)可能因此误判依赖关系。
还要注意两点:
-
type="module"脚本默认启用严格模式,且支持顶层await,行为和传统脚本不同,迁移时得验证初始化逻辑 - 它和普通
defer脚本不在同一队列里:如果 HTML 里混着<script src="legacy.js" defer></script>和<script type="module" src="new.js"></script>,两者的执行时机接近但不严格同步
真正容易被忽略的是:defer 保证 DOM 就绪,但不保证资源就绪——比如 canvas 元素存在了,但图片还没加载完,getContext() 可能仍返回 null。



















