async适用于完全独立、不操作dom且无依赖的脚本,如分析工具;用错会导致referenceerror,因其下载完立即执行,不等待dom就绪或依赖加载。

async 属性能提升第三方插件加载效率,但仅适用于完全独立、无依赖、不操作 DOM 初期结构的脚本;用错反而导致 ReferenceError 或执行失败。
async 属性到底什么时候该用
它让浏览器并行下载脚本、不阻塞 HTML 解析,但下载完就立刻执行——不管 DOM 是否就绪、其他脚本是否加载完毕。所以只适合:
- 纯工具类脚本(如
analytics.js、hotjar.js),不读取或修改页面元素 - 不依赖
jQuery、lodash等前置库的独立模块 - 不监听
DOMContentLoaded或window.onload的脚本 - 你确认它不会和页面内联脚本产生竞态(比如内联代码里直接调用了它暴露的函数)
常见误用:给 vue.runtime.esm-bundler.js 或 react.development.js 加 async,结果 Uncaught ReferenceError: React is not defined。
async 和 defer 混用时的坑
两者不能同时加在同一个 <script></script> 标签上,浏览器会忽略 defer、只认 async。更重要的是:
-
async脚本执行时机不可控,可能早于DOMContentLoaded,也可能晚于它 -
defer脚本一定在 DOM 解析完成后、DOMContentLoaded触发前按顺序执行 - 如果你的插件需要等
document.body存在(比如要appendChild一个浮层),别用async,改用defer或动态加载
典型错误写法:<script src="plugin.js" async defer></script> —— defer 形同虚设。
动态创建 script 标签比直接写 async 更可控
硬编码 async 是静态策略;而用 JS 动态插入,你能控制加载时机、做缓存、加超时、统一错误处理:
- 重复调用
loadScript('https://cdn.example.com/sdk.js')时,可先查缓存避免多次请求 - 能监听
onerror并 fallback 到备用 CDN:script.src = 'https://backup.cdn/sdk.js' - 可配合
IntersectionObserver,等用户滚动到某模块再加载对应插件,减少首屏请求数 - 支持传入
crossorigin或integrity属性,满足 SRI 安全要求
最小可用示例:
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = () => reject(new Error(`Load failed: ${src}`));
document.head.appendChild(script);
});
}
CDN 提供的 ?module 链接别乱加 async
像 https://cdn.jsdelivr.net/npm/dayjs@1/esm/index.js?module 这类 URL 是专为 import() 设计的 ESM 入口。如果强行用 <script async src="...?module"></script>,浏览器会报 Failed to execute 'importScripts' on 'WorkerGlobalScope': Module scripts don't support importScripts() 类似错误。
正确姿势只有两个:
- 用
import('https://cdn.jsdelivr.net/.../index.js?module')(需确保运行环境支持 ES 模块) - 用传统 UMD 版本,如
https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js,再配async
第三方 SDK 文档里写的 “Copy this script tag” 往往默认带 async,但没告诉你它背后假设了什么执行上下文——这点最容易被忽略。











