async & defer, what’s the differences?
Html引入外部脚本
Html中引入外部脚本的方式有很多种,最常见的就是使用<script>
标签,我们可以在<head>
或者<body>
中引入外部脚本。
1 | <head> |
1 | <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> 标签出现的顺序执行 |
注意事项:
- async脚本的执行无固定顺序,谁先下载完,谁先执行。
- defer脚本按出现的先后顺序执行。
以下代码中,我们使用defer加载两个脚本,其中short.js非常小,很快就下载完了,而long.js非常大,下载时间很长。因为defer脚本是按照书写顺序进行执行的,所以即使short.js先下载完了,也要等到long.js下载完毕才能开始执行。
1 | <script defer src='https://xxx/yyy/long.js'> |
用一张图来总结一下,图里中颜色含义如下:
绿色线条 - 表示dom解析
蓝色线条 - 表示脚本的下载
红色线条 - 表示脚本的执行。
script分为普通脚本和模块化脚本。
解释一下上面这张图:
- 第一行,普通脚本,没有指定async或者defer,下载和执行阶段会阻断dom解析。
- 第二行,普通脚本,有defer关键字,下载和执行阶段都不阻断dom解析。(下载和dom解析并行进行,执行在dom解析完成后开始)
- 第三行,普通脚本,有async关键字,下载阶段不阻断dom解析,但执行阶段有可能阻断dom解析(如果脚本已经下载完毕,但是dom解析尚未完成的情况下)
- 第四行,模块化脚本,默认包含defer属性,该脚本及其依赖的其他脚本的下载与dom解析平行进行,待dom解析完毕开始执行脚本。
- 第五行,模块化脚本,有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 | <body> |
async_script.js
1 | console.log('async script loaded'); |
defer_script.js
1 | console.log('defer script loaded'); |
答案:
1 | async script loaded |
多执行几次,你会发现,有时候输出还会是下面这样的。这充分印证了上面的结论,defer script一定在DOMContentLoaded之前执行,但是async script可能在DOMContentLoaded之前,也可能在其之后。
1 | defer script loaded |
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