async属性仅对带src的外部脚本生效,内联脚本加async会被浏览器完全忽略,仍同步执行并立即阻塞HTML解析;多个async脚本执行顺序不确定,且可能在DOM未就绪时运行导致document.getElementById返回null。

async 属性只对 src 外部脚本生效,内联脚本加了也白加
浏览器遇到 <script src="a.js" async></script> 时,并行下载脚本,同时继续解析 HTML;但遇到 <script async>console.log(1)</script>,async 被完全忽略,行为等同于同步脚本——它会立刻中断解析、执行代码。这是最常被误用的点:很多人以为加了 async 就“安全”,结果内联初始化逻辑仍导致 document.getElementById("app") 返回 null。
async 执行时机不可控,DOM 可能根本没影子
async 脚本下载完就执行,不管 <body> 解析到哪一步。哪怕脚本体积小、网速快,它也可能在 <html> 标签刚打开时就跑起来。此时 document.body 是 null,document.querySelector("main") 必然失败。
- 适合场景:只调用
navigator.sendBeacon()、fetch()上报,或加载广告 iframe,不读写任何 DOM 节点 - 不适合场景:
Vue.createApp()、document.addEventListener("click", ...)、依赖前序脚本导出的全局变量(如lodash) - 多个
async脚本执行顺序不确定——analytics.js可能比ads.js晚执行,但谁先下完谁先跑
async 和 defer 不能共存,写了 defer 也会被静默忽略
写成 <script src="a.js" async defer></script> 是无效的。规范明确:当两者同时存在,浏览器按 async 行为处理,defer 被丢弃。这不是兼容性问题,是标准行为。如果你看到某处文档建议“两个都加更保险”,那是在误导。
- 模块脚本(
type="module")默认自带defer语义,再加async或defer都无意义 - IE9 及更早版本不支持
async,会退化为同步加载;若需兼容,得用动态插入script标签兜底 -
async不解决执行阻塞——它只是把“阻塞点”从“下载+执行”挪到“执行瞬间”,HTML 解析仍会被中断
真正防阻塞的关键不是加属性,而是确认脚本是否真独立
很多团队给所有第三方脚本统一加 async,结果埋点 SDK 因依赖未初始化的全局配置对象而报错。判断依据只有两条:是否访问 <code>document 或 window 上非原生属性?是否调用其他脚本暴露的函数?只要有一条是,就不能用 async。
立即学习“前端免费学习笔记(深入)”;
- 统计类脚本通常安全:只用
navigator、performance、fetch,不碰 DOM,也不依赖其他 JS - 社交分享按钮、客服插件常不安全:内部可能尝试
document.getElementById("chat-widget")或等待window.jQuery - 最稳妥做法:查该 SDK 官方文档的“加载方式”章节,或直接看它源码里有没有
document.开头的调用



















