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

原文: flaviocopes.com/javascript-...

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

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

html 复制代码
<script src="script.js"></script>

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

一旦这个过程完成,解析就可以恢复,并且可以分析 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 都是布尔值树形,它们的用法类似:

html 复制代码
<script async src="script.js"></script>
html 复制代码
<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

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

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

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

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

head 标签中的 script 使用 async

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

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

head 标签中的 script 使用 defer

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

脚本是异步获取的,只有在 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 属性:

html 复制代码
<script defer src="script.js"></script>

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

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

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

相关推荐
东方小月24 分钟前
从零开发一个 Coding Agent(四):使用状态机校验大模型事件流
前端·人工智能·后端
Csvn42 分钟前
🧩 ESM vs CJS 混用的 7 个「天坑」——从 TypeScript 编译到 Node 与浏览器
前端
Csvn1 小时前
🎯 Web 性能 API 集合:Performance Observer 的 5 个冷门妙用
前端
Csvn1 小时前
深入 React 闭包陷阱:从根源上理解并根治 stale closure
前端
whyfail1 小时前
前端学 Spring Boot(8):接口为什么越用越慢?
前端·spring boot·后端
用户059540174462 小时前
LangChain 记忆测试踩坑实录:这两个坑让我排查了 4 小时
前端·css
程序员黑豆2 小时前
鸿蒙应用开发:@Monitor 装饰器使用教程
前端·harmonyos
SamChan903 小时前
在Web应用中集成PDF多语言翻译功能:PDFTranslator API实战指南
前端·python·ai·pdf·yapi·机器翻译
kyriewen3 小时前
AI Agent 9秒删光了生产数据库——我给自己的项目做了5个紧急检查
前端·ai编程·claude
IT_陈寒3 小时前
JavaScript的this又双叒叕让我怀疑人生了
前端·人工智能·后端