教程简介
- 1、阅读对象
只要对 js 基础掌握不牢的都适合 - 2、教程难度
初级「但是你不一定知道」,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢
正文
一、先有 DOM 才能操「扯」作「蛋」
JavaScript 要操作 DOM 前提是 DOM 要生成,上节说过 docuemnt 的 DOMContentLoaded、window.onload 页面生命周期方法都可以让 JavaScript 来进行操作 DOM,当然我们也可以尽可能的让 DOM 解析完然后再执行 JavaScript 代码,这就到了这节的重点–defer 和 async 属性
二、为什么要有 defer、async
JavaScript 外链式必须要有 src 属性
<script src="outer.js"></script>
如果没有 defer/async 属性,浏览器就会立即下载 outer.js 脚本,并且浏览器暂停解析,直到脚本下载完成并执行完成,浏览器继续解析,在这个过程中如果 outer.js 脚本非常大那下载过程会很慢,此时浏览器解析暂停着,给用户的感觉就是网页界面卡住了「体验很不好」
此时 async/defer 就有用武之地了
三、defer、async 的执行情况
先看看浏览器遇到 script 标签三种情况「外链式」
1、普通 script
<script src="outer.js"></script>
如图所示:普通的 script 加载外部 js 浏览器执行情况
- 1、浏览器进行页面解析「html 解析–生成 DOM 树」
- 2、当遇到 script 标签立即暂停解析,去下载 outer.js 脚本
- 3、脚本下载完毕,执行 outer.js
- 4、outer.js 执行完毕,浏览器继续解析 html
整个过程是一个串行的过程,前一个干完再干后一件事情,页面在暂停的时刻就是耗时之处
2、有 async 属性
<script src="outer.js" async></script>
从普通 script 标签我们了解到了页面解析和下载 js 是互拆的,那能不下载 js 的时候页面不暂停解析,当然可以,这就是 async 的作用「异步指的就是页面解析和下载 js 是异步的」,如图所示
浏览器执行情况如下:
- 1、浏览器进行页面解析「html 解析–生成 DOM 树」
- 2、当遇到 script 标签一看有 async 属性则下载 outer.js 脚本,此时解析 html 继续执行不中断
- 3、直到 outer.js 脚本下载完成,执行 outer.js 的时候浏览器暂停解析 html
- 4、直到 outer.js 执行完毕,浏览器继续解析 html
3、有 defer 属性
<script src="outer.js" defer></script>
从字面可以看出来 defer 是延迟加载的意思,其与 async 又不同,defer 是延迟 js 的执行「在界面解析完毕之后再执行 js 」并且界面解析和下载 js 同时进行,如下图所示:
浏览器的执行情况如下:
- 1、浏览器进行页面解析「html 解析–生成 DOM 树」
- 2、当遇到 script 标签一看有 defer 属性则下载 outer.js 脚本,此时解析 html 继续执行不中断
- 3、直到 outer.js 脚本下载完成此时不会立即执行 outer.js 而是继续解析 html
- 4、等 html 解析完毕,再执行 outer.js
四、Demo 验证
1、async 和普通 js 加载情况
(1)、创建以下几个文件
分别新建 index.html 文件,normal.js outer.js outer1.js 文件
(2)、各文件内容
- index.html 内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>js 延迟加载</title>
<!-- 使用 async 属性 -->
<script src="outer.js" async></script>
<!-- 使用 async 属性 -->
<script src="outer1.js" async></script>
<script src="normal.js"></script>
</head>
<body>
<div>
<ol>
<li>emel1</li>
<li>emel2</li>
<li>emel3</li>
<li>emel4</li>
</ol>
</div>
</body>
</html>
- normal.js 中直接输出 console.log(‘nromal’);
- outer.js 引入了 vue.js 的源码大概 10948 行代码「内容非常大,下载则耗时」
- outer1.js 直接输出 console.log(‘outer1.js’);
(3)、查看结果
多次运行得出结论 outer.js 内容输出总在 outer1.js 之后,为什么这样呢?由于 outer.js 的内容大下载耗时并且此处属性是 async 所以下载完后直接执行其中的 js 内容所以它相对来说后执行,async 就是谁先下载完谁先执行「乱序执行」
2、defer 和普通 js 加载情况
将上述 async 属性改为 defer 属性
<!-- 使用 defer 属性 -->
<script src="outer.js" defer></script>
<!-- 使用 defer 属性 -->
<script src="outer1.js" defer></script>
<script src="normal.js"></script>
我们来看看效果
js 外链有 defer 属性的会按顺序执行 js 的内容「下载 js 是异步的相对于浏览器解析」并且其执行机制基本上是在所有元素解析完成「顺序执行的」 DOMContentLoaded 事件触发之前执行的
3、async 遇到 DOMContentLoaded
1、修改 index.html
<script src="outer.js" defer></script>
<script src="outer1.js" async></script>
<script src="normal.js"></script>
将 outer.js 改为 defer
2、分别在 outer.js「在 vue 源码之后添加」 和 outer1.js 添加如下代码
// outer.js
document.addEventListener('DOMContentLoaded',function () {
let lis = document.getElementsByTagName('li') ;
console.log('outer.js-->li 的数量 '+lis.length) ;
})
下面是 outer1.js
// outer1.js
document.addEventListener('DOMContentLoaded',function () {
let lis = document.getElementsByTagName('li') ;
console.log('outer1.js-->li 的数量 '+lis.length) ;
})
查看结果
我们可以看到使用 async 和 defer 属性 DOMContentLoaded 都执行了,但是事实是如此吗?不是的,我们再修改 outer.js 为 async 查看结果
我们多刷新几次浏览器看到 outer.js 中有 DOMContentLoaded 的事件会出现没有调用的情况,为什么呢?那就是如果使用 async 属性时 DOMContentLoaded 事件有时会在 js 代码执行前调用,有时会在 js 执行后调用「此时会响应 DOMContentLoaded 事件」
很好理解,由于 async 属性是下载完 js 直接执行 js 中的代码,那么有两种情况「DOMContentLoaded 简称 DCL」:
- 1、浏览器还没有解析完 html,async 的 js 脚本下载完立即执行「此时 html 停止解析」,脚本执行完以后,浏览器继续解析完基本的 DOM 然后 DOMContentLoaded 事件响应「输出 DOMContentLoaded 中的代码」
- 2、浏览器解析完毕调用 DOMContentLoaded 事件「提前调用了」,此时 js 还没有下载完,「没有输出 DOMContentLoaded 中的代码」,js 都没有运行,想执行 DOMContentLoaded 事件肯定是执行不了的
所以建议使用 async 属性时不要使用 DOMContentLoaded 事件
4、defer 遇到 DOMContentLoaded
1、修改 index.htlm 中 outer.js 和 outer1.js 的属性为 defer
<script src="outer.js" defer></script>
<script src="outer1.js" defer></script>
<script src="normal.js"></script>
2、查看结果
则可以看到 DOMContentLoaded 中的代码执行了,由此可知 DOMContentLoaded 是在 html 解析完成 defer 脚本执行完后才执行的,其也分为两种情况
- 1、defer 下载完成,浏览器才解析完 html「生成基本 DOM」,然后执行 defer 脚本,再触发 DOMContentLoaded
- 2、浏览器解析完 html「生成基本 DOM」,defer 未载完直到下载完,并且执行完,再触发 DOMContentLoaded
五、总结
- 1、async 属性:下载 js 脚本不阻塞 html 解析,并且下载完 js 立即执行 js 脚本「并且多个 async 属性的 script 是乱序执行的」
- 2、defer 属性:下载 js 脚本不阻塞 html 解析,但执行 js 脚本是在 html 解析完毕之后「基本 DOM 生成」,并且多个 defer 属性的 script 是顺序执行的
- 3、js 脚本的执行和 html 解析永远都是互斥的
- 4、async 遇到 DOMContentLoaded,DOMContentLoaded 事件有可能在 async 脚本执行之前触发,也有可能在 async 脚本执行之后触发
- 5、defer 遇到 DOMContentLoaded,DOMContentLoaded 事件是在 defer 脚本执行完毕后触发
本节到此为止,我们下节见