🧙‍♂️轻轻一个字母差别,就能把首屏时间砍半——为什么90%的人还不知道?

98 阅读4分钟

🧭 引言:为什么脚本加载如此重要?

在现代 Web 应用中,JavaScript 的加载方式直接影响页面的首屏渲染时间、交互响应速度和用户体验。据统计,脚本阻塞导致的渲染延迟是页面性能瓶颈的主要来源之一。

为此,HTML5 引入了 deferasync 两个属性,用于优化脚本的加载与执行时机。但许多开发者对它们的区别和使用场景仍感到困惑。本文将带你由浅入深,彻底掌握 defer 与 async 的原理、差异与实战技巧


🧱 一、基础知识:浏览器如何加载脚本?

在默认情况下,浏览器遇到 <script> 标签时会:

  1. 暂停 HTML 解析
  2. 立即下载并执行脚本
  3. 继续解析 HTML

这称为**“渲染阻塞”**,会导致页面白屏时间变长,尤其在脚本体积较大或网络较慢时。

✅ 解决方案:异步加载脚本,即使用 deferasync


⚙️ 二、defer 与 async 的定义与行为对比

特性deferasync
是否阻塞 HTML 解析❌ 不阻塞❌ 不阻塞
脚本执行时机文档解析完成后,DOMContentLoaded 前脚本下载完成后立即执行
执行顺序保持声明顺序不保证顺序(谁先下载完谁先执行)
适用场景依赖 DOM 或其他脚本独立脚本,如统计、广告
是否仅对外部脚本有效✅ 是✅ 是

📌 总结一句话:
defer 是“渲染完再执行”,async 是“下载完就执行”()


🧪 三、示例代码演示

✅ 使用 defer:按顺序延迟执行

<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Defer 示例</title>
  <script defer src="jquery.js"></script>
  <script defer src="main.js"></script>
</head>
<body>
  <h1>Hello, defer!</h1>
</body>
</html>
  • jquery.jsmain.js并行下载
  • 执行顺序固定:先 jQuery,再 main
  • 等 HTML 解析完成后才执行,可安全操作 DOM

✅ 使用 async:快速独立执行

<!-- index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Async 示例</title>
  <script async src="analytics.js"></script>
  <script async src="ad.js"></script>
</head>
<body>
  <h1>Hello, async!</h1>
</body>
</html>
  • analytics.jsad.js 谁先下载完谁先执行
  • 不保证顺序,也不等待 HTML 解析完成
  • 适合不依赖 DOM 或其他脚本的独立模块

🧠 四、深入原理:浏览器内部发生了什么?

🔄 事件时间线对比(简化版)

阶段默认脚本defer 脚本async 脚本
HTML 解析阻塞✅ 继续解析✅ 继续解析
脚本下载同步异步并行异步并行
脚本执行立即解析完成后按序下载完立即执行
触发 DOMContentLoaded等待脚本所有 defer 执行后不等待 async

🎯 五、应用场景与最佳实践

✅ 什么时候用 defer

  • 脚本需要操作 DOM
  • 脚本依赖其他脚本(如 jQuery → 插件)
  • 希望按顺序执行
  • 示例:主应用逻辑、UI 初始化、框架入口
<script defer src="framework.js"></script>
<script defer src="app.js"></script>

✅ 什么时候用 async

  • 脚本是独立的,不依赖其他代码
  • 希望尽早执行,不在意顺序
  • 示例:第三方统计、广告、A/B 测试、社交分享按钮
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>

❌ 不推荐的使用方式

错误做法原因
内联脚本deferasync会被浏览器忽略()
相互依赖的脚本使用 async可能导致运行时错误(顺序不确定)
模块脚本type="module")上使用 defer模块默认就是 defer,无需重复添加()

📊 六、性能对比:真实数据说话

根据 Web.dev 和 [Chrome DevTools] 的测试:

加载方式首屏时间脚本执行顺序白屏时间
默认阻塞保证
defer✅ 快✅ 保证✅ 短
async✅ 快❌ 不保证✅ 短(但可能闪烁)

💡 结论:优先使用 defer,除非脚本完全独立。


🧩 七、进阶技巧:组合使用与动态加载

1. 混合使用 deferasync

你可以在同一页面中组合使用二者,只要清楚它们的执行顺序:

<script defer src="lib.js"></script>     <!-- 先执行 -->
<script async src="tracker.js"></script> <!-- 可能早执行,但不依赖 lib -->
<script defer src="app.js"></script>     <!-- 等 lib 执行后执行 -->

2. 动态加载脚本(编程式)

function loadScript(src, async = true) {
  const script = document.createElement('script');
  script.src = src;
  script.async = async;
  document.head.appendChild(script);
}

loadScript('https://example.com/widget.js', true); // async
loadScript('/js/main.js', false); // defer-like behavior if inserted early

📚 八、总结:一张图记住所有区别

🖼️ 推荐保存下图,面试/开发必备!

[HTML 解析] ───────┬──────────────────────────────┐
                   │                              │
         ┌─────────▼──────────┐      ┌───────────▼──────────┐
         │  <script defer>     │      │  <script async>       │
         │  下载:异步         │      │  下载:异步           │
         │  执行:解析后按序   │      │  执行:下载完立即     │
         │  顺序:保证         │      │  顺序:不保证         │
         └─────────────────────┘      └──────────────────────┘

🧭 九、开发建议与 checklist

最佳实践速查表

任务推荐属性
主框架(React、Vue)defer
jQuery + 插件defer
网站统计(Google Analytics)async
广告代码async
不依赖 DOM 的 SDKasync
依赖 DOM 的初始化脚本defer

🔒 安全提示:
使用 async 加载第三方脚本时,建议加 integrity 属性防止篡改:

<script async src="https://example.com/sdk.js"
  integrity="sha384-oqVu..."></script>

📖 十、延伸阅读与工具


📣 结语:选择比努力更重要

🎯 defer 是默认的最佳选择,async 是独立脚本的加速器。

理解 deferasync 的差异,不仅能提升页面性能,更能避免潜在的执行顺序 Bug。希望本文能帮助你在实际项目中做出更明智的脚本加载决策!


💬 有任何疑问或补充,欢迎在评论区留言交流!