探索`defer`与`async`属性:JavaScript加载与执行时序的精确控制与差异

好的,下面就开始讲座内容:

JavaScript加载与执行时序的精确控制:deferasync属性详解

大家好!今天我们来深入探讨HTML中 <script> 标签的两个非常重要的属性:deferasync。这两个属性都用于控制JavaScript脚本的加载和执行时机,但它们的工作方式却截然不同。理解它们的差异对于优化网页性能至关重要,能够帮助我们更好地控制资源加载,避免阻塞渲染,提升用户体验。

1. 没有 deferasync 的默认行为

在深入研究 deferasync 之前,我们先回顾一下 <script> 标签在没有这些属性时的默认行为。

当浏览器遇到一个没有 deferasync 属性的 <script> 标签时,它会立即停止解析HTML文档,下载并执行该脚本。执行完毕后,浏览器才会继续解析HTML文档。这种行为被称为“阻塞解析”或“同步加载”。

例如:

<!DOCTYPE html>
<html>
<head>
  <title>默认脚本加载</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <script src="script1.js"></script>
  <p>This is some content.</p>
  <script src="script2.js"></script>
</body>
</html>

在这个例子中,浏览器会按照以下顺序执行:

  1. 解析 HTML 直到 <script src="script1.js"></script>
  2. 停止解析 HTML。
  3. 下载 script1.js
  4. 执行 script1.js
  5. 继续解析 HTML 直到 <script src="script2.js"></script>
  6. 停止解析 HTML。
  7. 下载 script2.js
  8. 执行 script2.js
  9. 继续解析 HTML。

这种默认行为在脚本较大或网络速度较慢时,会导致页面渲染延迟,用户会看到一个空白页面,直到脚本下载并执行完毕。这会严重影响用户体验。

2. defer 属性:延迟执行,保持顺序

defer 属性告诉浏览器不要阻塞HTML文档的解析。浏览器会继续解析HTML,并在后台下载带有 defer 属性的脚本。重要的是,带有 defer 属性的脚本会按照它们在HTML文档中出现的顺序执行,并且会在HTML文档解析完成之后,DOMContentLoaded 事件触发之前执行。

例如:

<!DOCTYPE html>
<html>
<head>
  <title>defer 属性</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <script src="script1.js" defer></script>
  <p>This is some content.</p>
  <script src="script2.js" defer></script>
</body>
</html>

在这个例子中,浏览器会按照以下顺序执行:

  1. 解析 HTML (同时在后台下载 script1.jsscript2.js)。
  2. HTML 解析完成。
  3. 执行 script1.js (确保 script1.jsscript2.js 之前执行)。
  4. 执行 script2.js
  5. 触发 DOMContentLoaded 事件。

defer 的关键点:

  • 非阻塞下载: 不阻塞 HTML 解析。
  • 顺序执行: 按照脚本在 HTML 中出现的顺序执行。
  • 执行时机: 在 HTML 解析完成之后,DOMContentLoaded 事件触发之前执行。

3. async 属性:异步执行,不保证顺序

async 属性也告诉浏览器不要阻塞HTML文档的解析。浏览器会继续解析HTML,并在后台下载带有 async 属性的脚本。但是,与 defer 不同的是,带有 async 属性的脚本一旦下载完成,就会立即执行,而不会等待HTML文档解析完成,也不会按照它们在HTML文档中出现的顺序执行。

例如:

<!DOCTYPE html>
<html>
<head>
  <title>async 属性</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <script src="script1.js" async></script>
  <p>This is some content.</p>
  <script src="script2.js" async></script>
</body>
</html>

在这个例子中,浏览器会按照以下顺序执行:

  1. 解析 HTML (同时在后台下载 script1.jsscript2.js)。
  2. script1.js 下载完成,立即执行。
  3. script2.js 下载完成,立即执行。
  4. HTML 解析完成。
  5. 触发 DOMContentLoaded 事件。

需要注意的是,script1.jsscript2.js 的执行顺序是不确定的,取决于哪个脚本先下载完成。

async 的关键点:

  • 非阻塞下载: 不阻塞 HTML 解析。
  • 无序执行: 下载完成后立即执行,不保证脚本执行顺序。
  • 执行时机: 下载完成后立即执行,可能在 HTML 解析完成之前或之后。

4. defer vs async:对比分析

为了更清晰地理解 deferasync 的差异,我们用表格进行对比:

特性 defer async
下载行为 非阻塞,在后台下载 非阻塞,在后台下载
执行顺序 按照脚本在 HTML 中出现的顺序执行 下载完成后立即执行,不保证顺序
执行时机 在 HTML 解析完成之后,DOMContentLoaded 之前 下载完成后立即执行,可能在 HTML 解析完成之前或之后
适用场景 依赖 DOM 结构或需要按顺序执行的脚本 独立性强,不依赖 DOM 结构,不需要按顺序执行的脚本
是否阻塞DOMContentLoaded 不阻塞 DOMContentLoaded 不阻塞 DOMContentLoaded

5. 使用场景分析

  • defer 的适用场景:

    • 当脚本依赖于 DOM 结构时,例如需要操作 DOM 元素的脚本。
    • 当脚本之间存在依赖关系,需要按照特定顺序执行时。
    • 例如,jQuery 库及其插件通常会使用 defer 属性,以确保 jQuery 库先加载,然后再加载插件。
    <script src="jquery.js" defer></script>
    <script src="plugin.js" defer></script>
  • async 的适用场景:

    • 当脚本不依赖于 DOM 结构,并且不需要按照特定顺序执行时。
    • 例如,统计分析脚本、广告脚本等通常会使用 async 属性,因为它们只需要在页面加载完成后尽快执行,而不需要关心 DOM 结构或执行顺序。
    <script src="analytics.js" async></script>
    <script src="ads.js" async></script>

6. 代码示例:演示 deferasync 的执行顺序

为了更直观地演示 deferasync 的执行顺序,我们创建一个简单的示例。

HTML 文件 (index.html):

<!DOCTYPE html>
<html>
<head>
  <title>defer vs async</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <script>
    console.log("Inline script before defer and async.");
  </script>
  <script src="defer1.js" defer></script>
  <script src="async1.js" async></script>
  <script src="defer2.js" defer></script>
  <script src="async2.js" async></script>
  <p>This is some content.</p>
  <script>
    console.log("Inline script after defer and async.");
    document.addEventListener('DOMContentLoaded', function() {
      console.log("DOMContentLoaded event fired.");
    });
  </script>
</body>
</html>

JavaScript 文件 (defer1.js):

console.log("defer1.js loaded and executed.");

JavaScript 文件 (defer2.js):

console.log("defer2.js loaded and executed.");

JavaScript 文件 (async1.js):

console.log("async1.js loaded and executed.");

JavaScript 文件 (async2.js):

console.log("async2.js loaded and executed.");

在浏览器中打开 index.html,查看控制台输出,你可能会看到类似以下的输出:

Inline script before defer and async.
Inline script after defer and async.
async1.js loaded and executed.
async2.js loaded and executed.
defer1.js loaded and executed.
defer2.js loaded and executed.
DOMContentLoaded event fired.

或者:

Inline script before defer and async.
Inline script after defer and async.
async2.js loaded and executed.
async1.js loaded and executed.
defer1.js loaded and executed.
defer2.js loaded and executed.
DOMContentLoaded event fired.

这个例子清晰地展示了以下几点:

  • 内联脚本会立即执行,按照它们在 HTML 中出现的顺序执行。
  • 带有 async 属性的脚本会异步加载和执行,它们的执行顺序是不确定的。
  • 带有 defer 属性的脚本会延迟加载和执行,它们会按照在 HTML 中出现的顺序执行,并且会在 DOMContentLoaded 事件触发之前执行。

7. 最佳实践

  • 优先使用 async 如果你的脚本不依赖于 DOM 结构,并且不需要按照特定顺序执行,那么优先使用 async 属性。这可以最大程度地减少脚本对页面渲染的阻塞。

  • 需要顺序或依赖 DOM 使用 defer 如果你的脚本依赖于 DOM 结构,或者需要按照特定顺序执行,那么使用 defer 属性。

  • 小脚本使用内联: 对于非常小的脚本,可以考虑使用内联脚本,这样可以减少 HTTP 请求,提高加载速度。

  • 模块化打包工具: 现代前端开发通常使用模块化打包工具(如 Webpack、Parcel、Rollup 等)来管理和优化 JavaScript 代码。这些工具可以自动处理脚本的加载和执行顺序,并进行代码分割和压缩,从而提高页面性能。

  • 监控和测试: 使用性能监控工具(如 Lighthouse、WebPageTest 等)来评估你的网页性能,并进行 A/B 测试,以确定最佳的脚本加载策略。

8. 兼容性

deferasync 属性在现代浏览器中都得到了广泛的支持。但是,为了兼容旧版本的浏览器,可以考虑使用以下方法:

  • 条件注释: 使用条件注释来为旧版本的 IE 浏览器提供不同的脚本加载方式。
  • polyfill: 使用 polyfill 来模拟 deferasync 属性的行为。

9. 一些需要注意的点

  • defer 属性只对外部脚本有效。如果 defer 属性用于内联脚本,则会被忽略。
  • 如果 <script> 标签同时包含 deferasync 属性,则 async 属性会被忽略,defer 属性生效。
  • type="module" 的 script 标签默认是 defer 的,无需显式声明。

deferasync 的使用场景总结

async适合独立的、不依赖DOM的脚本,而defer适合需要按顺序执行或操作DOM的脚本。选择正确的属性可以显著提升页面加载速度,改善用户体验。希望今天的讲解能帮助大家更深入地理解 deferasync 属性,并在实际开发中灵活运用。

理解deferasync,优化页面加载

deferasync是控制JavaScript加载和执行的重要属性,理解它们的工作原理和差异,能够帮助我们优化页面加载速度,提升用户体验。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注