defer脚本严格按html中书写顺序执行,仅对带非空src的外部脚本生效;动态插入、内联、空src或混用async均无效;依赖场景应统一用defer或type="module",二者不共享执行队列。

多个defer脚本的执行顺序由HTML书写位置决定
你不需要手动控制下载快慢或加等待逻辑,defer 的核心契约就是:所有带 src 和 defer 的外部脚本,**一定按它们在 HTML 中从上到下的出现顺序执行**,哪怕 app.js 比 vendor.js 下得快,也必须等 vendor.js 执行完才轮到它。
这意味着依赖关系必须靠写法来表达:
-
<script src="lodash.js" defer></script>必须写在<script src="utils.js" defer></script>前面,如果反过来,utils.js里用_.map就会报ReferenceError: _ is not defined - 构建工具(比如 Webpack、Vite)自动注入的 polyfill 或 runtime 脚本,如果插在最前面,可能把你的
vendor.js挤到后面,导致业务脚本先执行——得检查最终生成的 HTML 源码,确认顺序没被破坏 - 动态插入的
<script defer></script>(例如用document.createElement('script')创建后 append)不继承defer行为,浏览器当它是普通 async 脚本处理,顺序无法保证
defer只对带src的外部脚本生效
以下写法全部无效,defer 属性会被浏览器忽略:
- 内联脚本:
<script defer>init();</script>→ 立即执行,和没写defer一样 -
src为空或只有空格:<script src="" defer></script>或<script src="" defer></script>→ 触发同步阻塞加载,不是 defer -
defer="false"这类赋值写法 →defer是布尔属性,值被忽略,实际仍生效,但语义混乱,容易误导协作同学
真正起作用的只有两个条件同时满足:src 属性存在(且非空字符串)+ defer 属性出现(无值)。
别混用async和defer,尤其当有依赖时
如果你写了:
<script src="jquery.js" async></script><script src="plugin.js" defer></script>
那 plugin.js 可能等 DOM 解析完才执行,而 jquery.js 下载完就立刻跑——但此时 $ 还没挂到全局,plugin.js 执行时大概率报错。
更隐蔽的问题是:async 脚本和 defer 脚本不在同一个执行队列里,它们之间没有时序保障。只要存在依赖,就该统一用 defer,或者改用 type="module"。
type="module"脚本天然 defer,但和普通defer脚本不混队列
<script type="module" src="main.js"></script> 不需要也不该加 defer —— 它自带并行下载、DOM 就绪后按序执行、严格模式等行为。加了反而可能干扰构建工具的依赖分析。
但要注意:type="module" 脚本和普通 defer 脚本执行时机“接近”却不“同步”。比如:
<script src="legacy.js" defer></script><script type="module" src="new.js"></script>
两者都保证在 DOMContentLoaded 前执行,但不能假设 legacy.js 一定比 new.js 先执行完,也不能让它们互相读取对方导出的变量或全局状态。
真正容易被忽略的是:type="module" 默认启用严格模式,且支持顶层 await,迁移旧脚本时得验证初始化逻辑是否还成立——比如 this 在模块顶层是 undefined,而传统脚本里是 window。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











