async脚本不保证执行顺序,导致依赖调用报错;defer脚本按html顺序执行但仅限外部文件;type="module"默认defer且按import图谱执行,更可靠。

异步脚本执行顺序本身不“错”,但盲目依赖它来隐式表达依赖关系,必然导致 ReferenceError、TypeError 或 DOM 访问失败——这不是浏览器 bug,是开发者误读规范的结果。
async 脚本之间为什么总报 “xxx is not defined”?
因为 async 不保证执行顺序。哪怕 <script async src="utils.js"></script> 写在 <script async src="main.js"></script> 前面,只要 main.js 下载更快,它就先执行。
- 典型错误:在
main.js里直接调用utils.init(),但utils.js还没加载完 - 根本原因:async 脚本彼此独立,各自触发宏任务,浏览器不协调它们的执行时机
- 内联脚本加
async属性完全无效,<script async>init();</script>等价于同步执行,且可能因 DOM 未就绪而失败 - IE10 及以下不支持
async,若需兼容,必须降级为动态插入 +onload链式加载
defer 脚本真能按 HTML 顺序执行吗?
能,但有硬约束:所有 defer 脚本必须是外部文件(带 src),且必须按依赖顺序在 HTML 中声明。
- 错误写法:
<script defer src="main.js"></script>在<script defer src="lib.js"></script>前面 →$ is not defined - defer 不等待 async 脚本:即使
lib.js是 defer,analytics.js是 async,后者仍可能抢先执行并访问未定义的全局变量 - 旧版 IE(如 IE9)对多个 defer 脚本保序不可靠,实测存在乱序概率
- defer 脚本一定在
DOMContentLoaded前执行,但无法保证 DOM 中它后面的元素已解析完毕(除非元素在它之前)
type="module" 是不是更安全的替代方案?
是,但它改变的不只是执行时机,而是整个加载模型——模块默认 defer,且执行顺序由 import 图谱决定,而非 HTML 书写顺序。
-
type="module"脚本必须带src,内联模块无法import,仅限调试 - 多个
<script type="module" src="a.js"></script>和<script type="module" src="b.js"></script>按 HTML 顺序执行,但前提是它们没有相互import;一旦有依赖,浏览器会按拓扑排序串行执行,HTML 顺序被忽略 -
async和defer在type="module"上被忽略,写了也无效果 - 动态创建的
type="module"脚本(document.createElement('script'))脱离静态模块队列,执行时机不可控,甚至可能晚于DOMContentLoaded
混用 async/defer/内联脚本时最常踩的坑
内联脚本永远同步执行,且位置即执行点——这是最容易被忽略的确定性行为。
-
<script>console.log(document.body);</script>放在里,document.body为null;放在前则安全 - async 脚本执行时 HTML 解析可能暂停,
document.body存在,但具体元素未必已挂载 - 混合使用时,浏览器不维护跨类型执行优先级:defer 脚本不等 async,async 不让 defer,内联脚本也不管前后有没有其他属性
- 想确保某段逻辑在 DOM 就绪后运行,别依赖
DOMContentLoaded事件回调——应检查document.readyState,因为 async 脚本可能在该事件触发前或后执行,无法预测
真正复杂的点不在属性怎么写,而在你是否显式表达了脚本间的依赖:用 import 声明依赖,比靠 HTML 顺序和 defer 更可靠;用动态加载 + onload 回调链,比混用 async 和全局函数调用更可控。任何试图用加载属性“猜”执行顺序的做法,都会在弱网、缓存失效或浏览器版本差异下暴露问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











