async脚本无法保序,因浏览器按下载完成顺序执行;保序应全用defer(外链、按依赖顺序声明)或动态加载;内联脚本和document.write()使async/defer失效;IE9 defer不可靠需轮询检测;HTTP/2推送可能绕过属性,需配合preload。

async 脚本无法强制保序,这是设计使然
如果你指望 async 让多个外部脚本按 HTML 中的书写顺序执行,一定会失败。它根本不是为保序设计的:浏览器谁先下载完 script1.js 就先执行谁,script2.js 即使写在后面,也可能因体积小、CDN 响应快而抢先执行。常见错误现象是 ReferenceError: initApp is not defined——因为依赖函数在被调用时还没加载完。
依赖脚本必须避开 async,改用 defer 或动态加载
defer 是唯一原生支持保序的方案,但仅限于外部脚本且必须全部使用 defer。混合使用 async 和 defer 会破坏顺序保障,三者(同步、async、defer)完全独立调度。
- 所有依赖链上的脚本都加
defer,且按依赖顺序在 HTML 中声明:<script defer src="lib.js"></script>必须出现在<script defer src="app.js"></script>之前 - 若需运行时控制(比如等某个 API 返回后再加载后续脚本),用
document.createElement('script')动态插入,并监听script.onload回调再插入下一个 - ESM 模块场景下,直接用
import()替代 script 标签,天然支持依赖解析和顺序保证
内联脚本或 document.write() 会让 async/defer 失效
如果脚本里用了 document.write(),无论加没加 async 或 defer,浏览器都会忽略这些属性,并退回到同步阻塞行为——整个页面可能被清空重写。另外,defer 对内联脚本无效,<script defer>console.log('hi')</script> 等同于普通内联脚本,立即执行且不保序。
IE9 及更老浏览器中 defer 顺序不可靠
虽然规范要求 defer 保序,但 IE9 实测存在乱序概率,尤其在脚本加载受网络抖动影响时。如果仍需兼容 IE9,必须手动加检查逻辑,比如在后置脚本里轮询前一个脚本是否暴露了预期变量:if (typeof libReady !== 'undefined') { initApp(); } else { setTimeout(check, 50); }。现代项目基本可忽略这点,但遗留系统上线前务必实测。
立即学习“前端免费学习笔记(深入)”;
实际部署中最容易被忽略的是 HTTP/2 推送或预加载器(preload scanner)对async/defer 的干扰——推送的脚本不会触发预加载扫描,属性可能被绕过。真要保序,别只靠属性,得结合 <link rel="preload" as="script"> 显式声明优先级。



















