轻轻一个字母差别,就能把首屏时间砍半——为什么90%的人还不知道?
🧭 引言:为什么脚本加载如此重要?
在现代 Web 应用中,JavaScript 的加载方式直接影响页面的首屏渲染时间、交互响应速度和用户体验。据统计,脚本阻塞导致的渲染延迟是页面性能瓶颈的主要来源之一。
为此,HTML5 引入了 defer
与 async
两个属性,用于优化脚本的加载与执行时机。但许多开发者对它们的区别和使用场景仍感到困惑。本文将带你由浅入深,彻底掌握 defer 与 async 的原理、差异与实战技巧。
🧱 一、基础知识:浏览器如何加载脚本?
在默认情况下,浏览器遇到 <script>
标签时会:
- 暂停 HTML 解析
- 立即下载并执行脚本
- 继续解析 HTML
这称为**“渲染阻塞”**,会导致页面白屏时间变长,尤其在脚本体积较大或网络较慢时。
✅ 解决方案:异步加载脚本,即使用
defer
或async
。
⚙️ 二、defer 与 async 的定义与行为对比
特性 | defer | async |
---|---|---|
是否阻塞 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.js
和main.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.js
和ad.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>
❌ 不推荐的使用方式
错误做法 | 原因 |
---|---|
给内联脚本加 defer 或 async | 会被浏览器忽略() |
对相互依赖的脚本使用 async | 可能导致运行时错误(顺序不确定) |
在模块脚本(type="module" )上使用 defer | 模块默认就是 defer ,无需重复添加() |
📊 六、性能对比:真实数据说话
根据 Web.dev 和 [Chrome DevTools] 的测试:
加载方式 | 首屏时间 | 脚本执行顺序 | 白屏时间 |
---|---|---|---|
默认阻塞 | 慢 | 保证 | 长 |
defer | ✅ 快 | ✅ 保证 | ✅ 短 |
async | ✅ 快 | ❌ 不保证 | ✅ 短(但可能闪烁) |
💡 结论:优先使用
defer
,除非脚本完全独立。
🧩 七、进阶技巧:组合使用与动态加载
1. 混合使用 defer
与 async
你可以在同一页面中组合使用二者,只要清楚它们的执行顺序:
<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 的 SDK | async |
依赖 DOM 的初始化脚本 | defer |
🔒 安全提示:
使用async
加载第三方脚本时,建议加integrity
属性防止篡改:
<script async src="https://example.com/sdk.js"integrity="sha384-oqVu..."></script>
📖 十、延伸阅读与工具
- MDN
<script>
元素文档() - Web.dev: Efficiently load JavaScript
- Lighthouse:检测阻塞脚本
- [Chrome DevTools Coverage]:分析脚本是否被使用
📣 结语:选择比努力更重要
🎯 defer 是默认的最佳选择,async 是独立脚本的加速器。
理解 defer
与 async
的差异,不仅能提升页面性能,更能避免潜在的执行顺序 Bug。希望本文能帮助你在实际项目中做出更明智的脚本加载决策!
💬 有任何疑问或补充,欢迎在评论区留言交流!