Skip to content

一文搞懂 script 标签的 async、defer

1.<script>, <script async>, 和 <script defer> 的区别

<script>, <script async>, 和 <script defer> 是在 HTML 中用于引入 JavaScript 代码的标签。它们在加载和执行 JavaScript 代码时有一些区别。

在 HTML 中会遇到以下三类 script:

html
<script src="xxx"></script>
<script src="xxx" async></script>
<script src="xxx" defer></script>

那么这三类 script 有什么区别呢?

1.script:阻塞并请求和立即执行

浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当代码执行完毕后恢复解析。整个过程如下图所示:

img

可以看到,script 阻塞了浏览器对 HTML 的解析,如果获取 JS 脚本的网络请求迟迟得不到响应,或者 JS 脚本执行时间过长,都会导致白屏,用户看不到页面内容。

2.async script:异步请求,响应后立即阻塞执行

一句话:加载不阻塞 html 的解析,执行时阻塞

async 表示异步,例如七牛的源码中就有大量的 async 出现:

img

当浏览器遇到带有 async 属性的 script 时,请求该脚本的网络请求是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再立即进行解析,图示如下:

img

当然,如果在 JS 脚本请求回来之前,HTML 已经解析完毕了,那就啥事没有,立即执行 JS 代码,如下图所示:

img

所以 async 是不可控的,因为执行时间不确定,你如果在异步 JS 脚本中获取某个 DOM 元素,有可能获取到也有可能获取不到。而且如果存在多个 async 的时候,它们之间的执行顺序也不确定,完全依赖于网络传输结果,谁先到执行谁。

3.defer scrip:异步请求,响应后等待解析完毕后执行 ——> 相对好用!

defer 表示延迟,例如掘金的源码中就有大量的 defer 出现:

img

当浏览器遇到带有 defer 属性的 script 时,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析 HTML,一旦网络请求回来之后,如果此时 HTML 还没有解析完,浏览器不会暂停解析并执行 JS 代码,而是等待 HTML 解析完毕再执行 JS 代码,图示如下:

img

如果存在多个 defer script 标签,浏览器(IE9 及以下除外)会保证它们按照在 HTML 中出现的顺序执行,不会破坏 JS 脚本之间的依赖关系。

最后,根据上面的分析,不同类型 script 的执行顺序及其是否阻塞解析 HTML 总结如下:

img

2.type=“module”

1.基本介绍

<script type="module"> 是 HTML 中用于加载 JavaScript 模块的标签,它引入了 ECMAScript 模块化的概念。模块化使得 JavaScript 代码能够更好地组织、维护和重用,而不再需要将所有代码都写在一个单一的全局命名空间下。

在 script 标签中写 js 代码,或者使用 src 引入 js 文件时,这些js内容默认不能使用module形式,即不能使用import导入文件,但是我们可以再script标签上加上type=module属性来改变方式

注意:<script type="module"> 的行为和 <script defer> 基本一样,但是模块依赖关系也会被下载。

也就是说这个标签的 js 脚本也会在解析的时候异步加载,然后在文档解析完毕之后再按照顺序执行

2.type=“module” 与 async、defer 修饰符同时使用

<script type="module"> 为类似于 <script defer> ,但是模块依赖关系也会被下载

<script type="module" defer> 自然也和 <script defer>一样的

<script type="module" async> 行为类似于 <script async>,额外的模块依赖关系也会被下载 ——> 也就是说实际上 defer 和 async 的异步效果是强于 type=“module”的!

3.同时使用的例子

例题:

html
//模块1:
<script type="module" src=" ./test.js"></script>
//模块2:
<script type=" module" src="./test.js" defer></script>
//模块3:
<script src="./test.js" defer></script>

三者顺序应该是什么样子的?

这些代码片段涉及到在 HTML 中加载 JavaScript 模块的不同方式。让我逐个解释它们,并说明它们的顺序:

  1. 模块 1:
html
<script type="module" src="./test.js"></script>

这个片段会加载一个类型为"module"的 JavaScript 文件test.jstype="module" 告诉浏览器这是一个 ECMAScript 模块。脚本会被异步加载,但是在主文档解析完成之后、其他脚本执行之前执行。

  1. 模块 2:
html
<script type="module" src="./test.js" defer></script>

这个片段与前一个类似,但多了 defer 属性。脚本会被异步加载,defer 属性告诉浏览器在文档解析完成后再执行脚本,同时仍然保持脚本的顺序。这意味着 test.js 将在文档解析完毕后按顺序执行。——> 和模块 1 一样

  1. 模块 3:
html
<script src="./test.js" defer></script>

这个片段没有设置 type="module" 属性,所以它不会被视为 ECMAScript 模块。它会被当作经典的脚本标签来处理。defer 属性在这里仍然告诉浏览器在文档解析完成后再执行脚本。同样地,这个脚本也会按照在文档中的顺序执行。——> 和模块 1 一样

因此,三者的执行顺序将取决于它们在文档中的出现顺序。如果它们按照你列出的顺序出现在文档中,那么它们的执行顺序将是模块 1 → 模块 2 → 模块 3。需要注意的是,模块 1 和模块 2 都被指定为 ECMAScript 模块,而模块 3 则不是。