多个defer脚本按HTML中<script>标签出现顺序执行,仅对带非空src的外部脚本生效;type="module"脚本天然具备defer行为,二者不可混用;defer只保证DOM就绪,不保证脚本内异步资源加载完成。

多个defer脚本的执行顺序由HTML书写位置决定
浏览器不看文件名、大小或网络响应快慢,只按 <script> 标签在 HTML 中出现的先后顺序执行。比如:<script src="utils.js" defer></script> 写在 <script src="app.js" defer></script> 前面,那 utils.js 一定先执行完,app.js 才开始运行——哪怕 app.js 下载得更快。
常见破坏顺序的操作包括:
- 构建工具(如 Webpack 的
html-webpack-plugin)自动把 polyfill 插入到最前,把你的vendor.js挤到了后面 - 用
document.write()动态生成<script defer>标签——现代浏览器已禁用该方式,且动态脚本不继承defer语义 - 手误把依赖方写在前面:
<script src="main.js" defer></script>放在<script src="lodash.js" defer></script>前,结果main.js执行时报ReferenceError: _ is not defined
defer只对带src的外部脚本生效,内联脚本加了也白加
<script defer>init();</script> 中的 defer 属性会被浏览器完全忽略,脚本仍会阻塞解析、立即执行——此时 DOM 还没建好,document.getElementById('app') 很可能返回 null。
真正起作用的只有两个硬性条件:
立即学习“前端免费学习笔记(深入)”;
-
src属性必须存在且非空(src=""或src=" "都等同于同步阻塞) -
defer是布尔属性,不能赋值(defer="false"依然生效,只是语义混乱)
所以正确写法只能是:<script src="init.js" defer></script>。
type="module" 脚本和 defer 不要混用
<script type="module" src="main.js"></script> 天然具备 defer 行为:并行下载、DOM 就绪后执行、严格按声明顺序运行。再显式加 defer 属于冗余,部分构建工具(如 Vite 的模块分析器)可能因此误判依赖关系。
更关键的是:type="module" 和普通 defer 脚本不在同一执行队列里。如果 HTML 中同时存在:
<script src="legacy.js" defer></script><script type="module" src="new.js"></script>
两者执行时机接近但不严格同步,别让它们互相假设对方已执行完毕。
defer不保证资源就绪,只保证DOM就绪
defer 确保脚本执行时 document.body 和所有已声明的元素都存在,但它不管脚本内部加载的资源是否到位。比如:
-
tf.min.js加了defer,但model-loader.js里调用tf.loadGraphModel()仍可能失败——因为模型文件本身还没下载完 -
app.js里用import()动态导入一个大模块,这个导入行为不受defer约束,仍可能触发白屏或竞态
真正容易被忽略的是:你控制得了脚本执行时机,但控制不了它内部发起的异步操作。这类问题得靠代码层兜底,比如检查 tf.ready()、加 loading 状态、或用 try/catch 捕获 import() 失败。



















