async适合完全独立的外部脚本,defer是业务脚本默认选择;二者混用会导致执行顺序失控,动态插入脚本需手动处理加载错误,type="module"默认等效defer且更优。

用 async 或 defer 属性能显著减少脚本对 HTML 解析和页面渲染的阻塞,但必须按脚本用途选对属性,否则反而引发 document.getElementById 返回 null、$ is not defined 等错误。
async 适合完全独立的脚本
只用于不操作 DOM、不依赖其他脚本、失败也不影响主流程的外部脚本,例如:
- 统计类:analytics.js、hotjar.js、fullstory.js
- 错误监控:error-tracker.js(仅监听
window.onerror) - 上报类:调用
navigator.sendBeacon()或fetch()的埋点脚本
注意:async 脚本一下载完就执行,不管 <body> 是否解析到对应元素。它不保序、不等 DOM,内联脚本加 async 无效。
defer 是业务脚本的默认选择
适用于需访问 DOM 元素、有加载顺序依赖的脚本,比如轮播图初始化、表单验证、在线编辑器(Monaco/CodeMirror)主库加载:
立即学习“前端免费学习笔记(深入)”;
- 下载时不阻塞 HTML 解析
- 一定在 DOM 解析完成之后、
DOMContentLoaded触发之前执行 - 多个
defer脚本严格按 HTML 中书写顺序执行
关键细节:defer 只对外部 src 脚本生效;内联初始化代码(如 monaco.editor.create())必须封装后,在 DOMContentLoaded 中调用,或放在最后一个 defer 脚本末尾。
避免混用 async 和 defer
二者调度机制互不兼容,混用会导致执行顺序失控:
-
<script src="lib.js" defer></script>→ 等 DOM 完成、按序执行 -
<script src="init.js" async></script>→ 下完立刻执行,可能早于lib.js任何阶段
结果常是 ReferenceError: myUtils is not defined。现代浏览器遇到 <script async defer src="a.js"></script> 会直接忽略 defer,按 async 处理。
动态插入脚本要主动控制时机与错误
JS 动态创建 <script>(如点击后加载地图 SDK)默认等效于 async=true:
- 不阻塞解析,但执行时机不可控
- 必须手动监听
onload和onerror,否则加载失败静默,极难调试 - 若需保序或等 DOM,应改用
import()或封装为 Promise 链
type="module" 脚本默认行为等效于 defer,还支持静态分析和预加载,新项目建议优先采用。



















