async 仅使外部脚本下载不阻塞 html 解析,但执行时机不确定,可能早于或晚于 domcontentloaded,且不保证执行顺序;仅适用于完全独立的脚本,如统计、监控类 sdk。

async 属性到底让脚本“异步”在哪?
它只保证下载不阻塞 HTML 解析,但不保证执行时机——下载完立刻执行,可能打断 DOM 构建,也可能在 DOMContentLoaded 之前或之后运行,完全取决于网速和脚本大小。
常见错误现象:document.getElementById 返回 null,因为脚本执行时 DOM 还没解析到对应元素;或者依赖的全局变量(如 jQuery)尚未加载就报 $ is not defined。
-
async仅对**外部脚本**生效(即带src的<script></script>),内联脚本加了也无效 - 多个
async脚本**执行顺序不确定**,不保证按 HTML 中的书写顺序执行 - 它和
defer互斥——同时写上,async优先级更高,defer被忽略
什么时候该用 async,而不是 defer 或普通 script?
适用于完全独立、不操作 DOM、不依赖其他脚本、也不被其他脚本依赖的场景,比如统计埋点、广告 SDK、错误监控等。
使用场景举例:加载 Google Analytics 的 gtag.js、Sentry 的初始化脚本、第三方客服 widget。
- ✅ 合适:
<script async src="https://www.googletagmanager.com/gtag/js?id=GA-XXX"></script> - ❌ 不合适:
<script async src="app.js"></script>(若app.js里调用了document.querySelector('header')) - ⚠️ 注意:即使脚本本身没 DOM 操作,若它动态
document.write或修改document.head,仍可能引发竞态问题
async 脚本中如何安全访问 DOM?
不能靠“等一会儿”,得主动监听状态。最稳妥的方式是监听 DOMContentLoaded,但要注意:如果脚本下载极快,可能在事件触发前就已执行,所以需兼容两种情况。
document.addEventListener('DOMContentLoaded', init, { once: true });
if (document.readyState === 'loading') {
// 尚未触发事件,先注册
} else {
// DOM 已就绪,立即执行
init();
}
function init() {
const el = document.getElementById('main');
if (el) el.classList.add('loaded');
}
- 不要用
window.onload——它等资源(图片、CSS)全部加载完,太晚 - 避免轮询
document.body,既低效又不可靠 - 如果脚本必须早于
DOMContentLoaded执行(如 patch 原生 API),那就不能用async,得改用defer或放在底部
async 在不同浏览器中的实际表现差异
现代浏览器(Chrome 80+、Firefox 70+、Safari 15.4+)对 async 的实现基本一致:并行下载 + 下载完成立即执行。但老版本 Safari(iOS 12 及更早)存在一个隐藏坑:它会把多个 async 脚本串行下载,大幅拖慢整体加载速度。
- 可通过 Network 面板观察
Initiator列确认是否真正并行(应为parser,而非other) - 若需兼容 iOS 12 以下,且脚本间无依赖,可手动用
fetch+eval(不推荐)或拆成多个<script async></script>并确保 CDN 支持 HTTP/2 多路复用 - Node.js 环境或 SSR 场景下,
async属性无意义——它只对浏览器 parser 生效
analytics.js 看似独立,却悄悄读取了 window.dataLayer——而这个对象由另一个非 async 脚本提前挂载。这种耦合不会报错,但会导致数据丢失,且难以复现。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











