前端秘密武器:async 与 defer,你用对了吗?
在前端开发的江湖里,HTML、CSS 和 JavaScript 就是我们手中的三把利刃。而在使用 HTML 引入 JavaScript 文件时,async
和 defer
这两个属性就像是隐藏在利刃中的神秘符文,掌握它们的奥秘,能让我们的代码在性能和加载顺序上如鱼得水。今天,咱们就来揭开这两个属性的神秘面纱,看看它们到底有啥区别,又该怎么根据需求来选择使用。
开篇:前端性能的痛点
在正式介绍 async
和 defer
之前,咱们先聊聊前端开发中一个让人头疼的问题------页面加载性能。想象一下,你打开一个网页,页面半天都加载不出来,你是不是会急得直跺脚?这其中很大一部分原因可能就是 JavaScript 文件的加载和执行方式出了问题。
在 HTML 中,当浏览器遇到 <script>
标签时,默认情况下会暂停 HTML 的解析,去下载并执行 JavaScript 文件,直到这个文件执行完毕,才会继续解析 HTML。这就好比你在盖房子,盖到一半突然停下来去买材料,等材料买回来用完了才接着盖,这速度能快吗?所以,为了解决这个问题,async
和 defer
这两个属性就应运而生了。
认识 <script>
标签
在 HTML 中,我们通常使用 <script>
标签来引入外部的 JavaScript 文件。基本的语法如下:
html
<!-- 这是一个简单的引入外部 JavaScript 文件的 <script> 标签 -->
<script src="example.js"></script>
这里的 src
属性指定了要引入的 JavaScript 文件的路径。当浏览器遇到这个标签时,会按照默认的方式来处理,也就是暂停 HTML 解析,下载并执行 JavaScript 文件。
深入了解 async
属性
什么是 async
属性?
async
是 <script>
标签的一个布尔属性,当你给 <script>
标签加上 async
属性后,浏览器会异步地下载 JavaScript 文件。也就是说,浏览器不会因为遇到这个 <script>
标签而暂停 HTML 的解析,而是会继续解析 HTML,同时在后台下载 JavaScript 文件。当文件下载完成后,会立即暂停 HTML 解析,执行这个 JavaScript 文件。
代码示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Async Example</title>
<!-- 这里使用了 async 属性,浏览器会异步下载这个 JavaScript 文件 -->
<script async src="async.js"></script>
</head>
<body>
<h1>Welcome to the Async World!</h1>
<p>This is a paragraph to demonstrate the async behavior.</p>
</body>
</html>
特点总结
- 异步下载:不会阻塞 HTML 解析,浏览器会在后台下载 JavaScript 文件。
- 立即执行:文件下载完成后,会立即暂停 HTML 解析,执行该文件。
- 加载顺序不确定 :如果有多个带有
async
属性的<script>
标签,它们的加载顺序是不确定的,取决于哪个文件先下载完成。
使用场景
async
属性适合那些不依赖于其他脚本文件,并且不需要按照特定顺序执行的脚本。比如一些第三方的广告脚本、分析脚本等,这些脚本通常只需要在页面加载过程中某个时刻执行即可,不需要和其他脚本有严格的执行顺序。
深入了解 defer
属性
什么是 defer
属性?
defer
同样是 <script>
标签的一个布尔属性。当你给 <script>
标签加上 defer
属性后,浏览器会异步地下载 JavaScript 文件,但是不会立即执行,而是会等到 HTML 解析完成后,再按照 <script>
标签在 HTML 中出现的顺序依次执行这些脚本。
代码示例
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Defer Example</title>
<!-- 这里使用了 defer 属性,浏览器会异步下载这个 JavaScript 文件 -->
<script defer src="defer.js"></script>
</head>
<body>
<h1>Welcome to the Defer World!</h1>
<p>This is a paragraph to demonstrate the defer behavior.</p>
</body>
</html>
特点总结
- 异步下载 :和
async
一样,不会阻塞 HTML 解析,浏览器会在后台下载 JavaScript 文件。 - 延迟执行:文件下载完成后,不会立即执行,而是等到 HTML 解析完成后再执行。
- 顺序执行 :如果有多个带有
defer
属性的<script>
标签,它们会按照在 HTML 中出现的顺序依次执行。
使用场景
defer
属性适合那些依赖于 HTML 结构,并且需要按照特定顺序执行的脚本。比如一些需要操作 DOM 元素的脚本,因为这些脚本需要在 HTML 解析完成后才能正确地找到并操作相应的 DOM 元素。
async
与 defer
的对比
加载和执行顺序对比
为了更直观地理解 async
和 defer
的区别,我们来看一个对比示例。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Async vs Defer</title>
<!-- 第一个带有 async 属性的脚本 -->
<script async src="async1.js"></script>
<!-- 第二个带有 async 属性的脚本 -->
<script async src="async2.js"></script>
<!-- 第一个带有 defer 属性的脚本 -->
<script defer src="defer1.js"></script>
<!-- 第二个带有 defer 属性的脚本 -->
<script defer src="defer2.js"></script>
</head>
<body>
<h1>Async vs Defer Comparison</h1>
<p>This is a paragraph to demonstrate the difference between async and defer.</p>
</body>
</html>
在这个示例中,两个带有 async
属性的脚本会异步下载,下载完成后会立即执行,它们的执行顺序不确定,取决于哪个文件先下载完成。而两个带有 defer
属性的脚本会异步下载,下载完成后不会立即执行,而是等到 HTML 解析完成后,按照在 HTML 中出现的顺序依次执行,也就是先执行 defer1.js
,再执行 defer2.js
。
性能影响对比
从性能角度来看,async
和 defer
都能提高页面的加载性能,因为它们都允许浏览器在后台异步下载 JavaScript 文件,不会阻塞 HTML 解析。但是,由于 async
脚本下载完成后会立即执行,可能会在 HTML 解析过程中打断解析,导致页面渲染出现卡顿。而 defer
脚本会等到 HTML 解析完成后再执行,不会影响页面的渲染,所以在某些情况下,defer
可能会有更好的性能表现。
根据需求选择 async
或 defer
不需要特定顺序且不依赖 DOM 的脚本
如果你有一些脚本不依赖于其他脚本,也不需要操作 DOM 元素,只需要在页面加载过程中某个时刻执行即可,那么可以使用 async
属性。比如:
html
<!-- 引入第三方广告脚本,使用 async 属性 -->
<script async src="https://example.com/ads.js"></script>
需要特定顺序且依赖 DOM 的脚本
如果你有一些脚本依赖于其他脚本,或者需要操作 DOM 元素,那么应该使用 defer
属性。比如:
html
<!-- 引入一个需要操作 DOM 的脚本,使用 defer 属性 -->
<script defer src="dom-manipulation.js"></script>
多个脚本的情况
如果有多个脚本,并且它们之间有依赖关系,需要按照特定顺序执行,那么应该使用 defer
属性。如果这些脚本之间没有依赖关系,并且不需要按照特定顺序执行,那么可以使用 async
属性。
实战案例分析
案例一:优化页面加载性能
假设你有一个电商网站,页面上需要引入一些第三方的分析脚本和一些自定义的 JavaScript 脚本。分析脚本不需要和其他脚本有严格的执行顺序,而自定义脚本需要操作 DOM 元素。那么可以这样优化:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>E-commerce Website</title>
<!-- 引入第三方分析脚本,使用 async 属性 -->
<script async src="https://example.com/analytics.js"></script>
<!-- 引入自定义脚本,使用 defer 属性 -->
<script defer src="custom.js"></script>
</head>
<body>
<h1>Welcome to our E-commerce Store!</h1>
<p>Explore our amazing products.</p>
</body>
</html>
案例二:解决脚本依赖问题
假设你有一个网站,需要引入 jQuery 库和一些依赖于 jQuery 的自定义脚本。由于自定义脚本依赖于 jQuery,所以需要确保 jQuery 先加载并执行。可以这样做:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Website with jQuery</title>
<!-- 引入 jQuery 库,使用 defer 属性 -->
<script defer src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入依赖于 jQuery 的自定义脚本,使用 defer 属性 -->
<script defer src="custom-jquery.js"></script>
</head>
<body>
<h1>Welcome to our Website!</h1>
<p>Check out our cool features.</p>
</body>
</html>
那么 ,在实际项目开发中使用
依据脚本依赖关系进行选择
- 无依赖的脚本 :当脚本不依赖于其他脚本,并且执行顺序不会影响页面功能时,可使用
async
属性。像第三方广告脚本、社交媒体分享按钮脚本等,它们独立运行,无需与其他脚本协同工作。
html
<!-- 引入第三方广告脚本,使用 async 属性 -->
<script async src="https://example.com/ads.js"></script>
- 有依赖的脚本 :若脚本依赖于其他脚本,或者需要在特定脚本之后执行,那么使用
defer
属性。比如 jQuery 库和依赖于它的自定义脚本,要保证 jQuery 先加载并执行。
html
<!-- 引入 jQuery 库,使用 defer 属性 -->
<script defer src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入依赖于 jQuery 的自定义脚本,使用 defer 属性 -->
<script defer src="custom-jquery.js"></script>
根据脚本对 DOM 的操作需求来选择
- 不操作 DOM 的脚本 :对于那些不涉及 DOM 操作的脚本,使用
async
属性可以让脚本在后台异步下载,下载完成后立即执行,不会影响页面的渲染。
html
<!-- 引入不操作 DOM 的脚本,使用 async 属性 -->
<script async src="analytics.js"></script>
- 操作 DOM 的脚本 :需要操作 DOM 元素的脚本,要确保在 HTML 解析完成后执行,此时应使用
defer
属性。
html
<!-- 引入需要操作 DOM 的脚本,使用 defer 属性 -->
<script defer src="dom-manipulation.js"></script>
多个脚本的情况
- 无顺序要求 :若多个脚本之间没有依赖关系,且不需要按照特定顺序执行,可都使用
async
属性,这样能并行下载,加快页面加载速度。
html
<!-- 多个无依赖关系的脚本,使用 async 属性 -->
<script async src="script1.js"></script>
<script async src="script2.js"></script>
- 有顺序要求 :当多个脚本有依赖关系,需要按照特定顺序执行时,都使用
defer
属性,它们会在 HTML 解析完成后按顺序依次执行。
html
<!-- 多个有依赖关系的脚本,使用 defer 属性 -->
<script defer src="base.js"></script>
<script defer src="module1.js"></script>
<script defer src="module2.js"></script>
结合性能测试和优化
- 在实际项目中,要通过性能测试工具(如 Google PageSpeed Insights、Lighthouse 等)来评估使用
async
和defer
属性后的页面性能。根据测试结果,对脚本的加载方式进行调整和优化。 - 对于首屏加载至关重要的脚本,可考虑将其内联到 HTML 文件中,避免额外的网络请求。而对于非首屏所需的脚本,使用
async
或defer
属性进行异步加载。
考虑兼容性
虽然现代浏览器对 async
和 defer
属性的支持良好,但在一些旧版本的浏览器中可能存在兼容性问题。在开发时,要进行充分的兼容性测试,必要时提供降级方案。
综上所述,在实际项目开发中,要根据脚本的依赖关系、对 DOM 的操作需求、多个脚本的执行顺序、性能测试结果以及兼容性等多方面因素,综合考虑使用 async
和 defer
属性,以达到最佳的页面加载性能和用户体验。
总结
在前端开发中,合理使用 async
和 defer
属性可以显著提高页面的加载性能和用户体验。async
属性适合那些不依赖于其他脚本,并且不需要按照特定顺序执行的脚本;而 defer
属性适合那些依赖于 HTML 结构,并且需要按照特定顺序执行的脚本。通过深入理解这两个属性的区别,并根据具体需求选择合适的属性,你就能让你的前端代码更加高效、稳定。
希望这篇文章能帮助你掌握 async
和 defer
属性的使用,让你在前端开发的道路上越走越顺!如果你还有其他问题,欢迎在评论区留言讨论。