async脚本一加载就报referenceerror是因为它下载完立即执行,不等待其他脚本加载或变量定义;例如lodash.js未加载完时app.js已执行,导致_.debounce未定义。

async脚本为什么一加载就报ReferenceError
因为async不保序也不等DOM——它下载完立刻执行,完全不管其他脚本有没有加载、有没有定义变量。比如你写了两个<script async src="lodash.js"></script>和<script async src="app.js"></script>,但lodash.js体积大、网络慢,app.js先下完就直接执行,_.debounce还没挂到window上,自然报ReferenceError: _ is not defined。
多个外部库有依赖关系时,async根本不能用
只要存在“B依赖A”这种关系,就别给它们都加async。浏览器不会帮你排队,也不会等A执行完再跑B。常见错误包括:
- 把
vue.js和main.js都设成async,结果main.js里new Vue()找不到Vue - 第三方SDK(如微信
js-sdk)和你的初始化逻辑混用async,导致wx.config调用失败 - 构建工具自动注入的
runtime.js被async打乱顺序,后续模块找不到__webpack_require__
想用async又得保序?只能拆开手动控制
真要异步加载但又要顺序执行,就得放弃纯HTML声明式写法,改用JS动态加载+链式等待:
- 用
document.createElement('script')创建脚本,设置src后监听onload事件 - 在第一个脚本的
onload回调里再插入第二个,形成串行链 - 或者用
Promise.all([import('a.js'), import('b.js')]),但注意这要求服务端支持CORS且脚本是ESM格式 - 避免在
onload里直接调用全局函数——有些SDK(如百度统计)会延迟注册变量,建议加setTimeout或轮询检查
示例:
const loadScript = (src) => new Promise(r => {
const s = document.createElement('script');
s.src = src;
s.onload = r;
document.head.appendChild(s);
});
loadScript('lodash.js').then(() => loadScript('app.js'));
最省事又安全的方案:换defer,不是换写法
如果你的多个外部库之间有明确依赖(比如utils.js → service.js → app.js),直接把它们全改成defer,并按依赖顺序写在HTML里:
<script src="utils.js" defer></script><script src="service.js" defer></script><script src="app.js" defer></script>
注意三点:
- 必须带
src,内联脚本加defer无效 - 不能和
async混用在同一页面——哪怕只一个async脚本,也可能在defer队列执行前就抢跑 - 如果其中某个库内部用了
import()或fetch加载子模块,defer管不了那个异步过程,仍需运行时判断
真正容易被忽略的是:defer只保证脚本本身按序执行,不保证它内部的动态导入也完成。比如app.js里await import('./feature.js'),这个feature.js加载完成时间不在defer控制范围内。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











