async 和 defer
HTML加载过程
1. 两个引擎
浏览器的引擎可以分为渲染引擎和 JS 引擎。 JS 引擎相对独立,而渲染引擎又包括 HTML 解释器、 CSS 解释器、布局、图形、视频、图片解码器等。
JS 引擎独立于渲染引擎,而浏览器加载网页需要 JS 引擎和渲染引擎相互协作。一般情况下,当 HTML 解释器遇到 <script>
标签时,浏览器会将控制权交给 JS 引擎,JS 引擎对内联的代码会直接执行,对外部的 JS 文件需要先下载再执行。当 JS 引擎执行完毕,浏览器又会将控制权交给渲染引擎,继续构建 CSSOM 和 DOM。
但这种写作方式显然有问题,当下载 JS 文件时,会阻塞渲染引擎的工作导致页面加载的延迟,所以才有了 async 和 defer。
HTML文档解析的三种情况
就 JS 同步执行而言,HTML 文档的解析会遇到三种情况:
- HTML
此时,渲染引擎会直接将 HTML 元素解析成 DOM 树然后进行渲染等操作,在 DOM 树生成的同时触发 DOMContentLoaded
事件。
- HTML+CSS
此时,仍然会将 HTML 元素解析成 DOM树,并在 DOM 树生成的同时触发 DOMContentLoaded
事件,但是不同的是,渲染树的生成在有 CSS 参与时,是通过 DOM + CSSOM 共同来渲染的,所以渲染过程的开始是在 DOM 和 CSSOM 都生成完成之后。
- HTML+CSS+JS
同步的情况下,解析 HTML 元素时,如果遇到<script>
标签,会有两种操作:获取 JS 代码、执行 JS 代码。如果此时正在生成 CSSOM,那么获取 JS 代码的操作可以和 CSSOM 的构建同时执行,但是只有菜 CSSOM 构建完成之后才能执行 JS 代码。如果<script>
标签先于 CSS,那么就不存在等待 CSSOM 的过程。
**获取 JS 代码:**获取操作可以和 CSSOM 的构建同时执行。如果是内联 JS ,则不存在下载的操作,就只有执行操作,如果是外部 JS 文件,那么此时会去下载 JS 文件。
**执行 JS 代码:**需要等待 CSSOM 构建完成之后才可以执行。
首屏时间
首屏时间可以理解为 DOMContentLoaded 事件的触发时间。另外,JS 代码写在 HTML 文件的头部和尾部(前)对首屏时间没有影响。其作用是确保 JS 代码执行时, DOM 和 CSSOM树已经解析完毕,在获取元素时不会出现获取为 null 的情况。
普通加载
普通加载就是上文所介绍的同步加载,不再赘述。
写法:
<script src="javascript.js"></script>
async
意义:异步加载 JS 文件,加载完成后直接执行。
写法:
<script src="javascript.js" async></script>
既然只是异步加载,那么加载完成后可能的结果:
- HTML 文档解析完毕
- HTML 文档未解析完毕
其实大部分情况下,async 加载的 JS 脚本会在 HTML 解析完毕之后执行。因为涉及到下载,而 HTML 是直接解析当前的 HTML 文件,所以如果 HTML 和 CSS 中的元素太多太复杂,导致 CSSOM 和 DOM 的解析时间过长,这个时候就会出现 JS 文件下载完毕准备执行时,HTML 文档仍然没有解析完毕。
所以,大部分情况下,不想阻塞 HTML 的解析时,使用 async 就够用了,如果涉及到 JS 脚本的执行顺序或者是为了更加严谨起见,可以使用 defer。
defer
意义:异步加载 JS 文件,全部 JS 文件加载完毕后,且 HTML 文档解析完毕之后,按顺序执行 JS 文件,最后触发 DOMContentLoaded 事件。
写法:
<script src="javascript.js" defer></script>
过程如下:
这里有几个点需要注意:
- 执行 JS 文件是按顺序执行的(特殊情况除外,后文有讲)
- 执行 JS 文件是在 HTML 文档解析完毕之后,但是实在触发 DOMContentLoaded 事件之前
- 同样是异步加载 JS 文件,不会阻塞 HTML 文档的解析
defer不按顺序执行
有时候即使写了 defer 也不会按照顺序执行,但是多个 JS 文件之前又确实存在先后的依赖关系。此时的解决方案是只写一个 defer ,也就是只包含一个延迟脚本。解决方法又两个,一个是写到一个 JS 文件中,使用 defer 加载这个 JS 文件。另外一个方法就是依赖文件使用同步的方式加载,后执行的 JS 文件使用 defer 异步加载。
如:
<script src="first.js" defer></script>
<script src="seconde.js" defer></script>
改成:
<script src="first.js"></script>
<script src="seconde.js" defer></script>
上一篇: mysql空间速度慢