async让脚本下载不阻塞HTML解析,但下载完成立即执行且不保证顺序;适用于无依赖、不操作DOM的独立脚本,如统计埋点;不适用依赖其他脚本或DOM结构的场景。

async 属性到底让脚本怎么“异步”
async 不是让脚本延迟执行,也不是让它在后台默默下载完再运行——它只做一件事:**下载不阻塞 HTML 解析,但一旦下载完成就立即执行,且不保证执行顺序**。这意味着:<script async src="a.js"></script> 和 <script async src="b.js"></script> 谁先下完谁先跑,哪怕 b.js 写在后面。
适用场景很明确:独立、无依赖、不操作 DOM 初期结构的脚本,比如统计埋点、广告 SDK、第三方分析工具。
- 如果脚本依赖
jQuery或其他库,别用async(除非你确保那个库也用了async且一定更早执行完——这几乎不可能) - 如果脚本要读取或修改刚解析到的 DOM 元素(比如
document.getElementById('header')),async可能报Cannot read property 'xxx' of null——因为 HTML 还没解析到那个元素,脚本已经执行了 - 多个
async脚本之间没有执行时序保障,a.js里定义的函数,b.js不能安全调用
async 和 defer 的关键区别在哪
两者都“不阻塞解析”,但执行时机天差地别:defer 会等到整个 HTML 解析完成、DOM 构建完毕后,再按书写顺序依次执行;async 是“一下载完就打断当前解析,立刻执行”。
- 想确保脚本在
DOMContentLoaded前执行,且依赖 DOM 结构?用defer,不是async - 脚本体积小、纯计算、无 DOM 依赖、也不依赖其他脚本?
async启动更快,可能比defer更早执行 -
async对内联脚本无效(<script async>console.log(1)</script>中的async被忽略) - IE9- 完全不支持
async,只支持defer(且 IE9 的defer有 bug:不保证顺序)
实操中容易被忽略的加载失败处理
async 脚本加载失败(404、网络中断、CSP 拦截)时,页面不会报错中断,但也不会触发任何回调——静默失败。你无法从 HTML 层感知它是否真正加载成功。
- 必须配合 JavaScript 监听:
document.addEventListener('error', e => { if (e.target && e.target.tagName === 'SCRIPT') console.warn('Async script failed:', e.target.src); }); - 不要把核心功能逻辑放在
async脚本里,除非你有 fallback 机制(比如检测全局变量是否存在,不存在则动态插入备用 CDN 或降级逻辑) - Webpack/Vite 打包时,
import()动态导入生成的 chunk 默认就是async行为,但错误可被捕获:import('./module.js').catch(err => {...}),这比纯 HTMLasync更可控
什么时候该放弃 async,改用动态 import()
当你要控制加载时机、需要错误处理、或脚本间有依赖关系时,<script async> 就力不从心了。现代方案是直接用 import():
if (shouldLoadAnalytics) {
import('./analytics.js')
.then(module => module.init())
.catch(err => console.error('Analytics load failed', err));
}它天然支持条件加载、Promise 链、错误捕获,且模块作用域隔离。虽然语法上不是 HTML 属性,但实际项目中,对灵活性和健壮性要求稍高的场景,import() 已成为比 async 更主流的选择。
真正难的不是加个 async,而是判断这个脚本是否真的适合“一来就抢着执行”——很多线上问题,根源在于开发者只看了“不阻塞”,却没想清楚“执行时机”和“依赖边界”。


















