有关<script> async defer
html javascript 性能优化 前端
把 css 文件放在页面顶部,把 js 文件放在页面底部是一个常用的优化网页加载速度的手段,因为 浏览器的解析规则是:如果遇到 script 标签,则暂停构建 DOM,转而开始执行 script 标签,如果是外部 script,那么浏览器还需要一直等待其「下载」并「执行」后,再继续解析后面的 HTML。
<script src="script.js"></script>
因此可以将这些外联脚本,放在 body 标签的最后面,确保先解析展示 body 中的内容,然后再一个个请求执行这些外联脚本。
defer 和 async,就是为了解决此类问题的。
async
<script async src="script.js"></script>
对于普通脚本,如果存在 async 属性,那么普通脚本会被并行请求,并尽快解析和执行。 对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。 该属性能够消除解析阻塞的 Javascript。 解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后才能继续解析。
有 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。
如果设置了 async 属性的 script 下载完之后,浏览器还没解析完 HTML,浏览器会暂停解析 HTML,立马执行此脚本,等执行完之后,再继续解析 HTML。
如果有多个 async 属性的 script 标签,浏览器会并行的在后台下载这些脚本,谁先下载完成,谁先执行。async 的特点是「完全独立」,不依赖其他内容。
当我们的项目,需要集成其他独立的第三方库时,可以使用此属性,他们不依赖我们,我们也不依赖于他们。通过设置此属性,让浏览器异步下载并执行他,比如Google Analytics。
注意
async属性仅适用于外部脚本,如果script脚本没有src,则会忽略async特性。
defer
<script defer src="myscript.js"></script>
这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。 有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。
有 defer时,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
如果 HTML 解析完成之后,设置了 defer 属性的脚本还没下载完成,浏览器会等脚本下载完成之后,再执行此脚本,执行完成之后,再触发 DOMContentLoaded 事件。
如果有多个设置了 defer 属性的脚本,浏览器会并行的在后台下载这些脚本,等 HTML 解析完成,并且所有脚本下载完成之后,再按照他们在 HTML 中出现的相对顺序执行,等所有脚本执行完成之后,再触发 DOMContentLoaded 事件。
注意
defer属性仅适用于外部脚本,如果script脚本没有src,则会忽略defer特性。defer属性对模块脚本(script type=‘module’)无效,因为模块脚本就是以defer的形式加载的。

总结
defer
- 不阻塞浏览器解析
HTML,等解析完HTML之后,才会执行script。 - 会并行下载 JavaScript 资源。
- 会按照
HTML中的相对顺序执行脚本。 - 会在脚本下载并执行完成之后,才会触发
DOMContentLoaded事件。 - 在脚本执行过程中,一定可以获取到
HTML中已有的元素。 - defer 属性对模块脚本无效。
- 适用于:所有外部脚本(通过
src引用的script)。
async
- 不阻塞浏览器解析
HTML,但是script下载完成后,会立即中断浏览器解析HTML,并执行此script。 - 会并行下载 JavaScript 资源。
- 互相独立,谁先下载完,谁先执行,没有固定的先后顺序,不可控。
- 由于没有确定的执行时机,所以在脚本里面可能会获取不到
HTML中已有的元素。 DOMContentLoaded事件和script脚本无相关性,无法确定他们的先后顺序。- 适用于:独立的第三方脚本。
defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
它俩的差别在于脚本下载完之后何时执行,defer 是最接近我们对于应用脚本加载和执行的要求的。
tip
如果一个 script 标签同时设置 defer 和 async,浏览器会如何处理?
async 的优先级比 defer 高,也就是如果同时存在这 2 个属性,那么浏览器将会以 async 的特性去加载此脚本。
分 2 种情况:
-
如果是「普通脚本」,浏览器会优先判断
async属性是否存在,如果存在,则以async特性去加载此脚本,如果不存在,再去判断是否存在 defer 属性。 -
如果是「模块脚本」,浏览器会判断
async属性是否存在:
- 如果存在,浏览器会并行下载此模块和他的所有依赖模块,等全部下载完成之后,会立刻执行此脚本。
- 如果不存在,浏览器也会并行下载此模块和他的所有依赖模块,然后等浏览器解析完
HTML之后,再执行此脚本(默认就是 defer 的效果)。 - 另外需要注意的是:在模块脚本上设置 defer 属性是无效的。
