【青训营】- 关于defer和async的一些思考

820 阅读3分钟

defer和async是什么?

将JavaScript 插入HTML的主要方法是使用 <script> 元素。 <script> 元素包含了8个属性,其中包括deferasync

在《javascript高级程序设计(第4版)》中对这两个属性的描述是这样的:

async:可选。表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其他脚本加载。只对外部脚本文件有效。
defer:可选。表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。在IE7及更早的版本中,对行内脚本也可以指定这个属性。

用一张图来说明:

3988476080-5fa27552a719e.jpg

两者都是用来控制<script> 资源的载入以及执行顺序。有defer属性的脚本会阻止DOMContentLoaded事件,直到脚本被加载且解析完成。

产生原因

那什么是DOMContentLoaded?当一个HTML文档被加载和解析完成后,DOMContentLoaded事件便会被触发。HTML文档被加载和解析完成后,会产生DOM(文档对象模型);如果有CSS则会生成CSSOM(CSS 对象模型),然后再由 DOM 和 CSSOM 合并产生渲染树。浏览器根据渲染树,知道了所有节点的位置布局以及样式,从而进行绘制。

Snipaste_2021-08-21_17-47-19.png

但Javascript可以堵塞DOM生成。传统写法中,当浏览器读到 <script> 时,便会暂停解析DOM,转而去下载 <script> 中的资源,并在下载完成后立刻执行。而这可能会导致操作DOM节点的错误;或是由于 <script> 资源得下载执行,导致画面暂时空白,产生不良的用户体验。

另外,因为 JavaScript 可以查询任意对象的样式,所以意味着在 CSS 解析完成,也就是 CSSOM 生成之后,JavaScript 才可以被执行。

虽然可以通过将<script>放到<body>前面解决,但由于同步原因,DOM都生成了才开始下载<script>的资源,也会造成用户的不良体验。而defer和async通过异步的方式解决了上述问题。

使用场景

1.defer

如果 <script>标签中包含defer,那么这一脚本将不会影响 HTML 文档的解析,而是等到 HTML 解析完成后才会执行。而 DOMContentLoaded 只有在defer脚本执行结束后才会被触发。对于不会生成任何网页内容的脚本,可以使用defer。

2.async

如果存在async属性,则脚本将会在可用时立即异步执行。与defer 的相同点是也会在后台执行下载,但不同的是当下载完成会马上暂停 DOM 解析(如果还没有解析完成的话),并开始执行 JavaScript。因此无法保证脚本的执行顺序。一般用于独立的小模块中,例如背景Logo页面广告等,在避免造成使用者体验变差的同时,尽量早的产生效果。

参考资料

浅析script 标签的 async 和 defer 属性
你不知道的 DOMContentLoaded