async脚本执行无序,易因依赖未就绪致referenceerror;defer按html顺序执行,适合有依赖的外部脚本;es模块通过import自动解析依赖,天然有序安全。

多个 <script></script> 标签异步加载时,执行顺序不保证,容易导致依赖关系被破坏,从而出现“变量未定义”错误。核心问题不是脚本下载慢,而是执行时机不可控——哪怕 utils.js 先下载完,也可能比 main.js 后执行。
async 属性让脚本下载不阻塞,但执行完全独立
给 <script async src="a.js"></script> 加了 async,浏览器会并行下载它,一旦下载完成就立刻执行,不管其他脚本是否就绪。如果 a.js 里用了 b.js 中声明的函数,而 b.js 还没执行,就会报 ReferenceError。
- 所有带
async的脚本彼此无序,也不按 HTML 中的书写顺序执行 -
async脚本不会等待 DOM 解析完成,可能在DOMContentLoaded前或后执行 - 适合完全独立、无依赖的脚本(如统计代码、广告 SDK)
defer 更适合有依赖的多脚本场景
defer 让脚本并行下载,但统一等到 HTML 解析完成、DOM 构建完毕后,再按 HTML 中的书写顺序 执行。这意味着只要把依赖项写在前面,就能保证执行顺序。
- 必须是外部脚本(
src属性存在),内联脚本加defer会被忽略 - 多个
defer脚本严格按出现顺序执行,天然支持依赖链 - 例如:
<script defer src="utils.js"></script>必定在<script defer src="main.js"></script>之前执行
用模块化方案彻底规避执行顺序问题
ES Module(type="module")默认就是异步且有序的:浏览器会自动解析 import 关系,构建依赖图,并确保被导入的模块先执行。
- 即使写成
<script type="module" src="main.js"></script>,其中import { helper } from './utils.js'也会让utils.js先加载并执行 - 模块脚本天然具有作用域隔离,不会污染全局,也避免意外覆盖变量
- 配合
import()动态导入,还能实现按需加载和更细粒度的控制
临时兜底:检查变量是否存在再使用
在无法立即重构加载方式时,可对关键依赖做运行时防御性检查,避免直接报错中断流程。
- 用
typeof myLib !== 'undefined'或typeof window.myLib === 'function'判断 - 结合
setTimeout或MutationObserver延迟重试(慎用,仅作降级) - 注意:这只是缓解手段,不能替代正确的加载策略











