async脚本可能让首屏更慢,因其下载完立即执行,常早于dom就绪,导致document.getelementbyid返回null、触发错误和重排;仅适用于完全独立的脚本(如analytics.js),依赖dom或其它脚本时应改用defer或动态加载。

async脚本为什么可能让首屏更慢而不是更快
加了async确实不阻塞 HTML 解析,但执行时机完全不可控——它一下载完就立刻执行,哪怕还没开始解析。此时调用document.getElementById或document.querySelector大概率返回null,脚本报错后虽不中断渲染,但可能触发重排、覆盖全局变量、甚至因错误逻辑导致后续 DOM 操作被跳过。
常见错误现象包括:
- 统计脚本里写了
document.body.appendChild,结果body还不存在,直接抛TypeError - 多个
async脚本中,plugin.js比jquery.js先下载完,执行时报$ is not defined - 脚本里含
console.timeStamp,发现它在DOMContentLoaded前 200ms 就执行了,但页面视觉内容还没出现
哪些脚本适合用async,哪些根本不能用
判断依据不是“要不要快”,而是“能不能独立运行”。async只适用于真正无依赖、不操作 DOM、不修改全局状态的脚本。
- ✅ 合适:
analytics.js、ads-sdk.min.js、error-tracking.js(仅上报、不读写 DOM) - ❌ 禁止:
main.js(初始化 Vue/React)、form-validator.js(绑定事件、查节点)、polyfill-loader.js(需在其他脚本前执行) - ⚠️ 风险场景:脚本里用了
document.write(已废弃)、或依赖window.jQuery等未声明的全局变量
验证方式:打开 Chrome DevTools → Network 面板,找该 JS 请求的 Initiator 列;如果是 parser,说明没生效;应为 other 或空。
async和defer混用时最容易踩的坑
一个页面里同时存在async和defer脚本,执行顺序就彻底失控。浏览器不会做任何协调——async脚本随时插队,defer脚本则老老实实排队等到最后。
- 现象:
<script async src="log.js"></script>和<script defer src="app.js"></script>同时存在,log.js里尝试读取app.js定义的window.APP_CONFIG,但总为undefined - 原因:
log.js可能在app.js下载完成前就执行了,而app.js要等整个 HTML 解析完才执行 - 解决:要么全用
defer(推荐),要么把log.js改成动态加载,在DOMContentLoaded后再插入
defer 对内联脚本无效:<script defer>console.log('hi')</script> 会被忽略,必须是带 src 的外链脚本。
async脚本的替代方案:什么时候该放弃它
当你的脚本需要访问 DOM、依赖其他库、或执行结果影响首屏视觉呈现时,async 就不是优化,而是埋雷。这时候该换策略。
- 优先用
defer:业务主逻辑、框架初始化、表单绑定,全部改用defer,既免阻塞又保顺序 - 改用动态加载:监听
click或scroll后再创建script元素,配合onload回调确保执行时机 - ES Module 天然适合:写成
<script type="module" src="main.mjs"></script>,浏览器自动按defer行为处理,还支持import()懒加载
最容易被忽略的一点:crossorigin 属性。跨域脚本(比如 CDN 上的 JS)用了 async 却没加 crossorigin,出错时堆栈信息全丢,只剩 Script error. ——调试时根本看不出问题在哪。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











