0%

async vs defer

async & defer, what’s the differences?

Html引入外部脚本

Html中引入外部脚本的方式有很多种,最常见的就是使用<script>标签,我们可以在<head>或者<body>中引入外部脚本。

1
2
3
4
5
<head>
<script src="./my_script.js"></script>
<script async src="./my_script1.js"></script>
<script defer src="./my_script2.js"></script>
</head>
1
2
3
4
5
<body>
<script src="./my_script.js"></script>
<script async src="./my_script1.js"></script>
<script defer src="./my_script2.js"></script>
</body>

那么从<head><body>中引入外部脚本有什么区别呢?

  • <head>中引入外部脚本,会阻塞Dom的解析,直到脚本下载完毕并执行完毕。
  • <body>末尾中引入外部脚本,不会阻塞Dom的解析,脚本下载和Dom解析并行进行。

你一定听说过上面的说法,但是上面的说法有个前提,那就是同步引入脚本,也就是不加async或者defer.

如果使用async或者defer关键字,那么在<head>中引入外部脚本和在<body>末尾引入外部没有太大区别。(只是<head>中的脚本会先于<body>中的脚本下载)

async & defer

在Html引入外部脚本时,可以使用async或者defer,那么两者有和区别呢?我们通过一个表格来分析一下:

关键字 下载时机 执行时机 是否阻断Dom解析 多个脚本的执行顺序
遇到对应的<script>标签,立即下载 下载后立即执行 是,下载阶段和执行阶段都阻断DOM解析。 按顺序执行
async 遇到对应的<script>标签,立即下载 下载后立即执行,只能保证在window.load事件之前执行,但是可能在window.DomContentLoade之前或之后。 没有固定顺序,取决于哪个脚本先下载完成。
defer 遇到对应的<script>标签,立即下载 在页面解析完之后,且在DomContentLoaded事件触发之前执行。 <script>标签出现的顺序执行

注意事项:

  1. async脚本的执行无固定顺序,谁先下载完,谁先执行。
  2. defer脚本按出现的先后顺序执行。

以下代码中,我们使用defer加载两个脚本,其中short.js非常小,很快就下载完了,而long.js非常大,下载时间很长。因为defer脚本是按照书写顺序进行执行的,所以即使short.js先下载完了,也要等到long.js下载完毕才能开始执行。

1
2
<script defer src='https://xxx/yyy/long.js'>
<script defer src='https://xxx/yyy/short.js'>

用一张图来总结一下,图里中颜色含义如下:
绿色线条 - 表示dom解析
蓝色线条 - 表示脚本的下载
红色线条 - 表示脚本的执行。
script分为普通脚本和模块化脚本。

https://html.spec.whatwg.org/images/asyncdefer.svg

解释一下上面这张图:

  1. 第一行,普通脚本,没有指定async或者defer,下载和执行阶段会阻断dom解析。
  2. 第二行,普通脚本,有defer关键字,下载和执行阶段都不阻断dom解析。(下载和dom解析并行进行,执行在dom解析完成后开始)
  3. 第三行,普通脚本,有async关键字,下载阶段不阻断dom解析,但执行阶段有可能阻断dom解析(如果脚本已经下载完毕,但是dom解析尚未完成的情况下)
  4. 第四行,模块化脚本,默认包含defer属性,该脚本及其依赖的其他脚本的下载与dom解析平行进行,待dom解析完毕开始执行脚本。
  5. 第五行,模块化脚本,有async关键字,该脚本及其依赖的其他脚本的下载与dom解析平行进行,下载完毕后立即执行脚本。

async & defer

  • 标记为async或defer的script,下载阶段都不会阻断Dom的解析,但是async是下载后立即执行,而defer是下载后且等待dom解析完毕才执行,所以两者唯一的区别就是:async脚本执行阶段可能会阻断dom解析(前提是脚本已经下载完毕,但dom解析尚未完成)。
  • module script默认包含defer属性
  • 多个标记为async的脚本,无法保证执行顺序。
  • 多个defer脚本按照script标签出现的顺序执行。
  • 没有标记async或defer的脚本会阻断Dom的解析。

window.load和window.DomContentLoaded

这是两个重要的事件,与async及defer的执行时机息息相关。

  • window.load - 标志整个页面全部加载完成,包括images,styles和JavaScript等所有外部资源。
  • window.DomContentLoaded - Html文件解析和加载完成(parsed and loaded),且所有标记为defer的js脚本全部下载并执行完成后触发,注意,该事件不会等待其他资源,比如images,subframes,或者标记为async的script下载完成。另外,该事件不会等待stylesheet完成,但是:因为defer脚本会等待stylesheet加载完才执行,而该事件又在defer脚本执行完才触发,所以如果有defer脚本存在的话,那么该事件一定会等待stylesheet加载完才触发。

问题来了

看起来async和defer没有太大的区别,那么两者分别在什么场合使用呢?

  • async一般用在与当前页面无关联的外部脚本,比如Google统计,计数脚本等。
  • defer一般用于需要操作当前页面的脚本,所以它需要等Dom解析完之后才执行。

一道小题

下面代码的输出结果是什么?

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
<div id="container"></div>
<script>
window.addEventListener("DOMContentLoaded", () => {
console.log("DOMContentLoaded event triggered");
});
window.addEventListener("load", () => {
console.log("load event triggered");
});
</script>
<script async src="./async_script.js"></script>
<script defer src="./defer_script.js"></script>
</body>

async_script.js

1
console.log('async script loaded');

defer_script.js

1
console.log('defer script loaded');

答案:

1
2
3
4
async script loaded
defer script loaded
DOMContentLoaded event triggered
load event triggered

多执行几次,你会发现,有时候输出还会是下面这样的。这充分印证了上面的结论,defer script一定在DOMContentLoaded之前执行,但是async script可能在DOMContentLoaded之前,也可能在其之后。

1
2
3
4
defer script loaded
DOMContentLoaded event triggered
async script loaded
load event triggered

References:

https://javascript.info/script-async-defer

https://html.spec.whatwg.org/multipage/scripting.html#attr-script-defer V8引擎

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event

https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event

https://javascript.info/onload-ondomcontentloaded