是的,defer 属性能保证同一文档中所有 defer 脚本按 html 出现顺序执行;但混用 async、动态插入、跨域加载失败或使用 document.write() 会破坏该顺序,且 defer 与 type="module" 脚本虽按位置排序,但模块内 import 不参与 defer 排队。

defer 属性是否保证脚本执行顺序
是的,defer 属性能保证多个 <script defer></script> 脚本按它们在 HTML 中出现的顺序执行,前提是它们都位于同一文档中、且都声明了 defer。这是 HTML 规范明确要求的行为,不是浏览器“碰巧”实现的。
哪些情况会破坏 defer 的顺序保证
看似加了 defer 就万事大吉,但实际容易踩坑:
- 混用
defer和async:只要有一个脚本用了async,它就可能提前执行并打断依赖链,defer脚本之间虽仍有序,但和async脚本之间无序 - 动态插入的
<script defer></script>:通过document.createElement('script')插入并设置defer,不会参与 HTML 解析时的顺序排队,其执行时机不可控 - 跨域脚本未声明
crossorigin且触发预加载失败:某些浏览器(如旧版 Safari)在资源加载异常时可能跳过该defer脚本,导致后续脚本仍按序执行,但逻辑已断 - 脚本内使用
document.write():即使有defer,一旦执行到document.write(),浏览器会终止解析并清空文档,彻底破坏顺序和 DOM 状态
defer 与模块脚本(type="module")的顺序关系
type="module" 脚本默认具有类似 defer 的行为(即延迟执行、按序、不阻塞解析),但它和传统 defer 脚本属于两套调度机制:
-
<script defer src="a.js"></script>和<script type="module" src="b.js"></script>的执行顺序由它们在 HTML 中的位置决定 —— 先写的先执行 - 但如果
a.js是普通脚本,b.js是模块,而b.js内部又import了c.js,那么c.js的加载和执行会等b.js开始执行后才触发,不再参与 HTML 层面的defer排队 - 所以,不要指望
defer和type="module"混写时还能维持统一的“全局顺序”,尤其当存在跨类型依赖(比如模块里要等某个defer脚本挂载的全局变量)时,极易出错
验证 defer 顺序是否生效的简单方法
不用查文档或猜行为,直接在控制台观察输出即可:
<script defer>console.log('1st');</script><script defer>console.log('2nd');</script><script defer>console.log('3rd');</script>
刷新页面,在开发者工具控制台中一定看到 1st → 2nd → 3rd 的严格顺序输出。如果中间夹了同步脚本、async 或者 document.write,这个链条就很容易被打破 —— 最容易被忽略的是那些看似无关的第三方 snippet,它们往往悄悄插入了非 defer 脚本。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











