译:HTML 脚本元素属性:async 与 defer 与 type=’module’

原文:https://webdeveloper.beehiiv.com/p/html-script-element-attributes-async-vs-defer-vs-typemodule
作者:Zachary Lee
译者:ChatGPT 4 Turbo

编者注:一图胜千言,建议拉到最后直接看图,一目了然。

我相信你对 HTML 中的 script 元素并不陌生。它有一些属性,例如 async、defer 等。你知道它们的作用以及它们之间的区别吗?

如果 script 元素没有添加任何属性,那么当浏览器遇到脚本时,会立即加载并执行它。在等待执行完成之后,才会继续解析后续的标签。这个阶段被称为解析 HTML。

如果脚本体积大或执行时间长,页面会长时间出现空白。就像上面的截图一样。这在一些 SPA 前端项目中尤其常见。

因此,针对这种情况,我们可以给 script 元素添加属性来进行优化。接下来,我将 script 元素分为三类,分别进行解释:

经典脚本

当我们对 script 元素省略 type 属性或配置为 JavaScript MIME 类型(目前唯一有效的值是 text/javascript)时,该脚本为经典脚本。

<script type="text/javascript" async></script>

为经典脚本配置 async 属性时,脚本会立即获取,但不会阻塞 HTML 的解析。一旦获取完成,它就会立即执行,如果这时 HTML 还没有解析完,就会被阻塞。

<script type="text/javascript" src="/test.js" defer></script>

当为经典脚本配置 defer 属性时,脚本会立即被获取但不会阻塞 HTML 解析。它会在 HTML 解析完成后才执行,但它会阻塞 DOMContentLoaded 事件。

应当注意的是,如果这个经典脚本没有使用 src 属性,即一个内联脚本,defer 属性将没有效果。

模块脚本

当我们为脚本元素配置 type="module" 时,该脚本就是一个模块脚本。

<script type="module"></script>

ESM 可以在模块脚本内部使用,并且即使相同的模块脚本被加载多次,该脚本只会被执行一次。

在加载机制上,其效果与经典脚本的 defer 相同。

值得注意的是,如果模块脚本配置了 src 属性,那么与经典脚本(允许跨域)不同,这个模块脚本需要使用 CORS 协议进行跨域获取。

<script type="module" async></script>

当为模块脚本配置 async 属性时,其加载机制与配置了 async 的经典脚本相同。

<script type="module" defer></script>

当为模块脚本配置 defer 属性时,此属性无效。因为模块脚本默认是“defer”的。

其他

当脚本元素的 type 属性值不是上述两个有效值时。那么这个脚本元素中的内容(包括 src)将被浏览器丢弃,不会被下载和执行。

结论

下面的截图清楚地展示了这些类型之间的差异:

图片来自 WHATWG