【译】使用 defer 和 async 高效地加载 JavaScript

554 阅读4分钟

原文: flaviocopes.com/javascript-…

在 HTML 页面中加载 JS 脚本文件时,需要注意不要损害页面的加载性能。将 JS 脚本添加到 HTML 页面的位置和方式会影响加载时间。

传统上,脚本通过以下方式包含在页面中:

<script src="script.js"></script>

当 HTML 解析器解析到这一行时,就会发出获取脚本的请求,并执行该脚本。

一旦这个过程完成,解析就可以恢复,并且可以分析 HTML 的其余部分。

可以想象,这个操作会对页面的加载时间产生巨大的影响。

如果脚本的加载时间比预期的要长一些,例如网络速度有点慢,或者您使用的是移动设备,连接有点不稳定,那么访问者可能会看到一个空白页,直到脚本加载完成并执行。

脚本在页面中的位置问题

当你第一次学习 HTML 时,你会被告知脚本标签位于 标签中:

<html>
  <head>
    <title>Title</title>
    <script src="script.js"></script>
  </head>
  <body>
    ...
  </body>
</html>

正如我前面所说,当解析器发现这一行时,它会去获取脚本并执行它。完成这项任务后,它会继续解析 body。

这样做不好,因为会产生大量延迟。解决这个问题的一个常用方法是将 script 标签放在页面底部,也就是结尾的 </body> 标签之前。

这样,script 脚本就可以在页面全部解析和加载完毕后再加载和执行,与将 script 脚本放在 head 标签中相比,这是一个巨大的提升。

如果你需要支持旧版浏览器,而这些浏览器又不支持 HTML 的两个相对较新的特性:asyncdefer,那么这就是您能做的最好的事情。

async 和 defer

async 和 defer 都是布尔值树形,它们的用法类似:

<script async src="script.js"></script>
<script defer src="script.js"></script>

如果同时指定这两个参数,在现代浏览器中 async 优先级更高,而支持 defer 但不支持 async 的旧版浏览器将退回到 defer。

For the support table, check caniuse.com for async caniuse.com/#feat=scrip… and for defer caniuse.com/#feat=scrip…

这些属性仅在页面 head 标签中使用 script 时才有意义,如果像我们上面看到的那样将 script 放在正 body 底部,则这些属性毫无用处。

性能比较

head 标签中的 script 不使用 defer 或 async

下面是将 script 放到页面 head 中加载脚本的方式,既没有使用 defer,也没有使用 async

without-defer-async-head

解析过程会暂停,直到获取并执行脚本。执行完毕后,继续解析。

body 标签中的 script 不使用 defer 或 async

下面是一个页面在不使用 ·deferasync 的情况下加载脚本的过程,将其放在 body 标签的末尾,就在 </body> 之前:

without-defer-async-body

解析过程没有任何停顿,解析完成后,脚本将被获取并执行。解析是在下载脚本之前完成的,因此用户看到的页面比上一个示例要早得多。

head 标签中的 script 使用 async

下面是一个页面使用 async 加载脚本,并将其放在 head 标签中:

with-async

脚本以异步的方式获取,获取脚本后,HTML 解析会暂停转而去执行 script 脚本,脚本执行完成后才会恢复 HTML 的解析。

head 标签中的 script 使用 defer

下面是一个页面使用 defer 加载脚本,并将其放在 head 标签中:

with-defer

脚本是异步获取的,只有在 HTML 解析完成后才会执行。

解析 HTML 结束的时间与我们将脚本放在 body 标签末尾的时间相同,但总的来说,脚本执行结束的时间要早得多,因为脚本被下载和HTML 解析是并行的。

因此,就速度而言,这是最理想的解决方案 🏆

阻塞解析

async 会阻止页面的解析,而 defer 不会。

阻塞渲染

asyncdefer 都不能保证阻塞渲染。这取决于你和你的的脚本(例如,确保脚本在 onLoad 事件后运行)。

domInteractive

标记为 defer 的脚本会在 domInteractive 事件后立即执行,而 domInteractive 事件会在 HTML 被加载、解析并构建 DOM 后发生。

此时,CSS 和图像仍处于解析和加载中的过程中。

一旦这个过程完成,浏览器将将会触发 domComplete 事件,然后触发 onLoad 事件。

domInteractive 非常重要,因为它的时间被认为是衡量加载速度的标准。更多信息,请参阅 MDN

保持顺序

另一种情况是 defer:标记为 async 的脚本在可用时按随意顺序执行。标记为 defer 的脚本会按照标记中定义的顺序执行(解析完成后)。

最佳实践

使用脚本时,加快页面加载速度的最佳方法是将脚本放在 head 标签中,并在 script 标签中添加 defer 属性:

<script defer src="script.js"></script>

这就是更快触发 domInteractive 事件的场景。

考虑到 defer 的优点,在各种情况下 async 似乎比异步更好。

除非你不介意延迟页面的首次呈现,否则请确保在解析页面时,你想要的 JavaScript 已经执行。