script标签中defer和async的区别是什么

46 阅读3分钟

核心对比表

特性无属性asyncdefer
下载行为立即下载并阻塞解析异步下载,不阻塞解析异步下载,不阻塞解析
执行时机下载后立即执行,阻塞解析下载完成后立即执行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:三个有依赖关系的库

<!-- 错误:async不保证顺序,可能jQuery还没加载就执行plugin -->
<script async src="jquery.js"></script>
<script async src="plugin.js"></script>  <!-- 依赖jQuery -->

<!-- 正确:defer保证顺序 -->
<script defer src="jquery.js"></script>
<script defer src="plugin.js"></script>  <!-- 保证jQuery先执行 -->

场景2:Google Analytics统计代码

<!-- 适合async:独立,无依赖,不操作DOM -->
<script async src="https://www.google-analytics.com/analytics.js"></script>

场景3:现代模块的最佳实践

<head>
  <!-- 关键CSS -->
  
  <!-- 非关键脚本用defer -->
  <script defer src="main.js"></script>
  
  <!-- 第三方独立脚本用async -->
  <script async src="analytics.js"></script>
</head>
<body>
  <!-- 内容 -->
</body>

现代开发建议

推荐做法

  1. 主要应用脚本使用defer
    • 保证DOM操作安全
    • 保持执行顺序
  2. 独立第三方脚本:使用async
    • 不影响主线程
    • 不依赖其他脚本
  3. 关键渲染脚本:内联或普通加载
    • 首屏渲染必须的极少量脚本

兼容性考虑

  • asyncdefer在支持它们的浏览器中都会被忽略(视为defer

TypeScript/ES6模块的特殊性

<!-- 模块脚本默认具有defer行为 -->
<script type="module" src="app.js"></script>
<!-- 等同于 -->
<script type="module" src="app.js" defer></script>

<!-- 可以强制async行为 -->
<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
   └─ 否:根据上述规则选择

记住要点

  1. defer = "下载不阻塞,执行等解析完,顺序有保证"
  2. async = "下载不阻塞,执行不等解析,顺序没保证"
  3. 现代Web开发中,defer是默认推荐选择,除非有特殊需求
  4. 内联脚本不需要这些属性(会被忽略)
  5. 使用type="module"时,浏览器会自动应用最佳实践