核心对比表
| 特性 | 无属性 | async | defer |
|---|
| 下载行为 | 立即下载并阻塞解析 | 异步下载,不阻塞解析 | 异步下载,不阻塞解析 |
| 执行时机 | 下载后立即执行,阻塞解析 | 下载完成后立即执行 | HTML解析完成后执行,在DOMContentLoaded之前 |
| 执行顺序 | 按文档顺序 | 不保证顺序(谁先下载完谁执行) | 保持文档顺序 |
| DOM操作 | 可能访问不到完整DOM | 可能访问不到完整DOM | 可以访问完整DOM |
| 适用场景 | 少量关键脚本 | 独立第三方脚本(分析、广告) | 依赖DOM的脚本,有顺序依赖的脚本 |
详细解析
1. 无属性的普通脚本
<script src="script.js"></script>
- 行为:立即暂停HTML解析 → 下载脚本 → 执行脚本 → 继续解析HTML
- 影响:显著影响页面加载性能
2. async(异步)脚本
<script async src="script.js"></script>
- 加载:与HTML解析并行下载
- 执行:下载完成立即执行,此时会阻塞HTML解析
- 特点:
- 不保证执行顺序(多个async脚本,先下载完的先执行)
- 可能在
DOMContentLoaded之前或之后执行
- 不适合依赖DOM的脚本(DOM可能还没准备好)
3. defer(延迟)脚本
<script defer src="script.js"></script>
- 加载:与HTML解析并行下载
- 执行:HTML解析完成后,按文档顺序执行
- 特点:
- 保证执行顺序(多个defer脚本,按出现的顺序执行)
- 在
DOMContentLoaded事件之前执行
- 可以安全操作DOM(DOM已完全解析)
执行时机图示
普通脚本: 解析HTML → █下载█ → █执行█ → 继续解析
async脚本: 解析HTML → (并行下载) → █执行█ (可能中断解析)
↓
█下载█
defer脚本: 解析HTML → 完成解析 → █执行█(按顺序) → DOMContentLoaded
↓
█下载█(并行)
实际示例
场景1:三个有依赖关系的库
<script async src="jquery.js"></script>
<script async src="plugin.js"></script>
<script defer src="jquery.js"></script>
<script defer src="plugin.js"></script>
场景2:Google Analytics统计代码
<script async src="https://www.google-analytics.com/analytics.js"></script>
场景3:现代模块的最佳实践
<head>
<script defer src="main.js"></script>
<script async src="analytics.js"></script>
</head>
<body>
</body>
现代开发建议
推荐做法
- 主要应用脚本:
使用defer
- 独立第三方脚本:使用
async
- 关键渲染脚本:内联或普通加载
兼容性考虑
async和defer在支持它们的浏览器中都会被忽略(视为defer)
TypeScript/ES6模块的特殊性
<script type="module" src="app.js"></script>
<script type="module" src="app.js" defer></script>
<script type="module" src="app.js" async></script>
性能优化建议
性能影响对比
<script src="heavy.js"></script>
<script async src="heavy.js"></script>
<script defer src="heavy.js"></script>
检测脚本加载性能
const script = document.createElement('script');
script.src = 'your-script.js';
script.onload = () => {
console.log('脚本加载完成');
};
document.head.appendChild(script);
总结决策树
需要脚本吗?
├─ 是首屏渲染必需的吗?
│ ├─ 是:内联或普通加载(无属性)
│ └─ 否:继续判断
├─ 是第三方独立脚本吗?(如分析、广告)
│ ├─ 是:使用 async
│ └─ 否:继续判断
├─ 脚本依赖DOM或其他脚本吗?
│ ├─ 是:使用 defer
│ └─ 否:async 或 defer 均可
└─ 是ES6模块吗?
├─ 是:默认 defer,需要立即执行加 async
└─ 否:根据上述规则选择
记住要点
- defer = "下载不阻塞,执行等解析完,顺序有保证"
- async = "下载不阻塞,执行不等解析,顺序没保证"
- 现代Web开发中,defer是默认推荐选择,除非有特殊需求
- 内联脚本不需要这些属性(会被忽略)
- 使用type="module"时,浏览器会自动应用最佳实践