理解 JavaScript 的 async
与 defer
属性:优化你的脚本加载
在现代 Web 开发中,优化页面加载速度是一项至关重要的任务。随着 JavaScript 代码量的增加,不合理的脚本加载方式可能严重拖慢页面渲染速度,影响用户体验。为了解决这一问题,HTML 提供了两个关键属性:async
和 defer
。这篇文章将深入探讨它们的使用场景和最佳实践,帮助你更高效地加载和执行脚本。
为什么需要 async
和 defer
在浏览器早期,JavaScript 是一种单线程、同步执行的语言。默认情况下,浏览器遇到 <script>
标签时,会暂停 HTML 的解析,下载并执行脚本后再继续解析文档。这种行为确保了 document.write()
等动态生成内容的代码能够正常工作,但也引入了以下问题:
- 阻塞解析:每次加载和执行脚本时,HTML 的解析都会暂停,影响页面渲染速度。
- 性能低效:多个脚本的加载与执行顺序严格依赖其在文档中的位置,造成额外的等待时间。
为了解决这些问题,HTML5 引入了 async
和 defer
属性,让浏览器能够以更高效的方式加载和执行脚本。
async
和 defer
的基础概念
这两个属性的共同点是:它们都告诉浏览器,当前脚本不会使用 document.write()
,因此不需要阻塞 HTML 的解析。这使得浏览器可以并行下载脚本和解析 HTML,但它们的行为在脚本执行时机上存在显著差异。
1. defer
属性
- 加载行为:脚本文件会与 HTML 解析同时下载。
- 执行时机:脚本的执行会延迟到 HTML 文档完全解析之后。
- 执行顺序:多个
defer
脚本会按照它们在文档中出现的顺序依次执行。
这种属性非常适合依赖完整 DOM 结构的脚本,比如初始化页面逻辑或绑定事件监听器的代码。
<script src="script1.js" defer></script>
<script src="script2.js" defer></script>
上面的脚本会按顺序执行:先运行 script1.js
,再运行 script2.js
,并且是在 HTML 文档完全解析之后才执行。
2. async
属性
- 加载行为:脚本文件会与 HTML 解析同时下载。
- 执行时机:脚本会在下载完成后立即执行,即使 HTML 文档尚未完全解析。
- 执行顺序:脚本的执行顺序取决于它们的下载完成时间,无法保证顺序。
这种属性适合独立的脚本(比如分析工具或广告脚本),它们不依赖其他脚本或 DOM 结构。
<script src="script1.js" async></script>
<script src="script2.js" async></script>
在这种情况下,script1.js
和 script2.js
的执行顺序是不确定的,因为它们会根据各自的下载完成时间立即执行。
async
与 defer
的对比
特性 | defer | async |
---|---|---|
加载方式 | 脚本与 HTML 解析同时进行 | 脚本与 HTML 解析同时进行 |
执行时机 | HTML 解析完成后 | 下载完成后立即执行 |
执行顺序 | 按文档中的出现顺序 | 不确定,取决于下载完成时间 |
适用场景 | DOM 依赖性强的脚本 | 独立脚本 |
模块化脚本的默认行为
对于声明为 type="module"
的脚本,浏览器默认会采用类似 defer
的行为:
- 脚本会与 HTML 解析同时下载。
- 脚本会在 HTML 文档解析完成后执行。
- 执行顺序按照文档中出现的顺序。
如果需要立即执行模块化脚本,可以通过添加 async
属性覆盖默认行为。
<script type="module" src="module1.js"></script>
<script type="module" src="module2.js"></script>
默认情况下,module1.js
会先于 module2.js
执行,但如果你添加了 async
,执行顺序会变得不可预测。
实践中的最佳策略
在实际开发中,如何选择 async
和 defer
属性?以下是一些常见的建议:
-
使用
defer
加载关键脚本
如果脚本需要操作 DOM 或依赖其他脚本,建议使用defer
,以确保其在文档解析完成后执行。 -
使用
async
加载独立脚本
独立于页面内容的脚本(如统计工具、广告脚本等)可以使用async
,以尽早完成加载和执行。 -
把不使用
async
或defer
的<script>
标签放在文档底部
对于直接嵌入 HTML 的脚本,将<script>
标签放在文档的</body>
前面可以避免阻塞页面渲染。
示例:如何高效加载脚本
假设你的页面需要加载以下三类脚本:
- 操作 DOM 的初始化脚本(
init.js
)。 - 独立的统计工具脚本(
analytics.js
)。 - 第三方广告脚本(
ads.js
)。
以下是推荐的加载方式:
<!-- DOM 依赖脚本 -->
<script src="init.js" defer></script>
<!-- 独立的统计脚本 -->
<script src="analytics.js" async></script>
<!-- 独立的广告脚本 -->
<script src="ads.js" async></script>
这样设置可以确保:
init.js
在 HTML 解析完成后执行,不影响页面内容。analytics.js
和ads.js
会尽早加载并执行,不互相干扰,也不阻塞 HTML 解析。
总结
async
和 defer
是优化 JavaScript 加载的强大工具,它们让我们能够平衡页面加载性能与脚本功能需求。在选择时,关键是理解脚本的依赖关系与执行顺序:
- 使用
async
优化独立脚本加载。 - 使用
defer
处理依赖 DOM 的脚本。 - 对模块化脚本,合理利用其默认
defer
行为。
掌握这些技巧后,你的页面加载性能将得到显著提升,同时也会避免因脚本加载顺序不当而产生的潜在问题。