async不保序因“谁先下载完谁先执行”,defer保序因“DOM解析完后按HTML顺序执行”;async适合独立统计脚本,defer适合依赖DOM或有先后依赖的业务逻辑。

async 和 defer 都不保证执行顺序,但原因完全不同
如果你指望 async 或 defer 让多个 <script> 按书写顺序执行,会踩坑。两者都绕过 HTML 解析阻塞,但调度机制差异极大:async 是“谁先下载完谁先执行”,完全独立;defer 是“等 DOM 解析完、按 script 出现顺序执行”。
常见错误现象:async 脚本 A 依赖 B,但 A 先加载完并执行,报 ReferenceError: B is not defined;defer 脚本看似顺序执行,但若混用 async 或内联脚本,顺序就不可控。
-
async仅适用于完全独立的脚本(如统计代码、广告 SDK) -
defer适用于依赖 DOM 或相互依赖的模块化脚本(如初始化逻辑、组件挂载) - 两者都不能用于动态生成
<script>的场景(如通过document.write或innerHTML插入),此时属性被忽略
defer 脚本一定在 DOMContentLoaded 前执行,但 async 不一定
defer 的执行时机是明确的:HTML 解析完成、DOM 构建完毕后,但在 DOMContentLoaded 事件触发前,按文档顺序执行所有 defer 脚本。而 async 脚本一旦下载完成就立即执行——可能在 DOM 解析中途,也可能在 DOMContentLoaded 之后。
这意味着:用 defer 可以安全操作 document,无需加事件监听;而 async 中访问 document.body 可能为 null,必须自己判断或包裹在 document.readyState 检查中。
立即学习“前端免费学习笔记(深入)”;
-
defer脚本内可直接写document.getElementById('app') -
async脚本中建议用if (document.body) { ... } else { document.addEventListener('DOMContentLoaded', ...) } - 如果页面极小、脚本极大,
async甚至可能比defer更晚执行(因下载耗时长)
混合使用 async/defer 时,加载行为互不影响但执行完全隔离
一个页面里同时存在 async、defer 和普通同步脚本,它们的加载和执行是三套平行机制:同步脚本阻塞解析;async 下载完即执行;defer 等解析完再排队执行。三者之间没有协调,也不互相等待。
典型陷阱:把工具库(如 lodash.js)设为 async,业务逻辑设为 defer,结果业务脚本先执行,报 _.map is not a function —— 因为 lodash 还没下载完。
- 不要用
async加载基础依赖,除非你确认它已缓存且极小 - 多个
defer脚本之间顺序可靠,但和async脚本无任何顺序保障 - 内联脚本(无
src)永远同步执行,不受async/defer影响
现代打包工具(如 Webpack/Vite)默认不生成 defer 脚本,需手动配置
构建工具输出的 <script> 标签默认无属性,即同步加载。即使你用 import() 动态导入,生成的 chunk 脚本也是通过 appendChild 注入,不带 defer。想让入口脚本延迟执行,得主动加属性或改模板。
Vite 用户常误以为 build.rollupOptions.output.manualChunks 自动带来 defer 行为,其实只是拆包,加载策略仍由 HTML 中的标签决定。
- Webpack:在
HtmlWebpackPlugin配置中设置scriptLoading: 'defer' - Vite:在
index.html中手动给<script type="module">加defer(注意:type=module 默认具备类似 defer 的行为,但非完全等价) - React/Vue CLI 项目:修改
public/index.html中的 script 标签,而非依赖构建配置
defer 只看 HTML 顺序。这两者混用时,顺序逻辑会变得模糊。



















