async脚本下载完立即执行,不等DOM就绪,导致document.getElementById常返回null;仅适用于完全独立、不操作DOM的脚本(如统计、广告),依赖DOM时应改用defer。

async 脚本执行时机不可控,别指望它等 DOM 就绪
用 async 的最大误区,就是以为“异步加载”等于“DOM 加载完再执行”。事实是:只要脚本下载完成,浏览器立刻中断 HTML 解析并执行它——此时 document.body 可能还不存在,document.getElementById('submit-btn') 必然返回 null。
常见错误现象:
- 控制台报错
Cannot read property 'addEventListener' of null - 页面部分功能失灵,但无明显报错(因绑定逻辑被跳过)
- 在 IE10–11 或某些低版本 Android WebView 中行为更不稳定
根本原因:async 只承诺“下载不阻塞”,不承诺“执行不早于 DOM 就绪”。哪怕脚本只有 1KB,网络延迟极低,也不能规避这个风险。
async 适合什么场景?只选完全独立、不碰 DOM 的脚本
判断一个脚本能否加 async,就看它是否满足全部三个条件:
立即学习“前端免费学习笔记(深入)”;
- 不访问
document、window以外的任何全局对象(比如不调用document.querySelector、localStorage.getItem) - 不依赖其他脚本的变量或函数(比如不引用 jQuery 的
$或自定义的utils.format) - 执行结果不影响首屏渲染或用户可感知交互(典型如埋点、广告、错误监控)
示例正确用法:
<script src="ga.js" async></script> <script src="adsense.js" async></script>
反例(绝对不要这么写):
<script src="init-ui.js" async></script> <!-- init-ui.js 里有 document.getElementById('nav').addEventListener(...) -->async 和 defer 混用时,defer 会被忽略
如果一个 <script> 标签同时写了 async 和 defer,现代浏览器(Chrome 80+、Firefox 70+、Safari 15.4+)会直接以 async 行为处理,defer 彻底失效。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
这意味着:
- 多个同时带
async defer的脚本,执行顺序依然不确定 - 你无法靠加
defer来“补救”一个本该等 DOM 的async脚本 - HTML 验证器会警告该组合无效,XHTML 下甚至可能解析失败
真正需要 DOM 就绪 + 顺序执行的脚本,只用 defer;需要极致加载优先级且完全独立的,才用 async。二者不是互补,而是互斥选项。
module 脚本默认 defer,async 不生效
当 <script type="module"> 遇到 async 属性时,浏览器会忽略它——因为 ES 模块天然具有 defer 语义:下载异步、执行延迟到 DOM 解析完成、按声明顺序执行。
所以这行代码:
<script type="module" src="app.mjs" async></script>
和这行实际效果完全一样:
<script type="module" src="app.mjs"></script>
如果你看到有人给 module 脚本加 async,基本可以判定他没理解模块加载机制。想提前执行模块逻辑?得靠动态 import(),而不是靠属性。
最常被忽略的一点:async 不是性能银弹。它只解决下载阻塞,却可能引入执行时机错乱、DOM 访问失败、调试困难等问题。真要优化首屏,优先考虑压缩脚本体积、启用 HTTP/2 多路复用、用 preload 提前拉取关键资源,而不是盲目加 async。


















