写之前先摆出结论
async: 异步加载下载js脚本文件,在加载下载完之后立马执行js脚本文件.多个async脚本文件执行时执行顺序没有保障.执行js过程中,会阻塞html的解析和渲染
defer: 异步加载下载js脚本文件,在加载下载完之后等到html解析完毕才执行js脚本文件.多个defer脚本文件执行时的执行顺序有保障.执行js过程中,会阻塞html的解析和渲染
async和defer的共同作用:
- 在加载和下载js的时候不会阻塞html的解析和渲染
二者的区别
- 执行的时间不同,async在下载或加载完成之后直接执行js脚本文件,但是defer会等到html完全的解析之后才会执行相对应的js文件. (注意点:在js文件执行的时候会阻塞html的解析和渲染))
- 如果存在多个async和defer的时候,defer的执行顺序是有保障的,async的执行顺序是没保障的.async的执行顺序是谁先加载或下载完毕谁先执行.不讲求先来后到的规矩.但是defer不同,它还是讲先来后到的关系.
补充:如果想要使用DOM最好使用defer,因为它在html解析完全之后才执行.而async的执行时间没有保障.它可能在html解析完全之前就执行了.不能获取到所有的DOM
下面开始实操
实操前的准备
工具: 谷歌浏览器. vscode
打开谷歌浏览器的F12 将磁盘缓存关闭,将下载速度调成slow 3G
这里通过使用外部引入jQuery和echarts的cdn来模拟外部引入js文件,模拟js文件的下载
首先来探究不加defer和async的时候.js的执行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://code.jquery.com/jquery-3.7.0.js" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.2/echarts.min.js" ></script>
</head>
<body>
<div>1</div>
</body>
</html>
首先要知道解析html是从头至尾进行解析的,浏览器会先处理head中的数据,在处理body中的数据.
所以浏览器一开始会接触到head中的script.浏览器会下载或加载js脚本文件.这个时候它既不是defer 的也不是async的.所以它下载和加载的时候会阻塞html的解析和渲染.
这个时候来看浏览器的瀑布图
发现,在jquery和echarts下载完毕之后.html的解析才陆陆续续的开始.
补充: 这个时候你打开浏览器的直观感受就是: 浏览器图标一直转,浏览器有一段白屏的时间.
这个时候我们引入defer或者async我们再看看效果.
- defer
- async
这里我们可以发现,在加载或下载js脚本文件的时候,浏览器可以正常的解析html.
这个时候浏览器的直观显示是,页面很快就出来了,不会有白屏.但是浏览器的图片一直会转(在下载和加载js脚本文件)
然后我们在探究defer和async的区别
- defer在html解析完之后执行,async在下载或加载完js脚本之后立马执行.
这里我们通过使用以下代码来完成实验
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./_1.js" defer></script>
<script src="./_2.js" async></script>
<script src="https://code.jquery.com/jquery-3.7.0.js" ></script>
</head>
<body>
<div>1</div>
<div>222222</div>
</body>
</html>
_1.js中是一句 console.log(1);
_2.js中是一句 console.log(2);
- 这里我们用到了之前的结论,不加defer和async的js脚本在加载或下载的时候会阻塞html的解析和加载.
- 而我们添加了defer的js脚本,浏览器碰到它会直接下载或加载它,但是它的执行会等到html都解析完全之后.
- 我们在这个例子里通过使用在head中使用一个不加defer和async的比较大的js脚本文件来模拟阻塞html的解析的过程.然后判断添加了defer的js脚本是否在html渲染完全之后再执行.(如果是添加了defer的脚本的执行时间会有延迟)
- 之前提及添加了async的脚本会在脚本加载和下载之后立即执行.所以无论你html是否被阻塞,都跟他的执行无关.所以它的执行会比之前添加了defer的快了很多.
控制台中显示的结果是
控制台很快的输出2,然后过了一段时间才输出1
原因: _1.js加载虽然在_2.js之前.但是_1.js的执行要在html解析之后,这个时候html的解析被另一个不加async和defer的脚本给阻塞了,所以执行会比_2.js慢很多.然后_2.js虽然加载在_1.js之后,但是它加载完立马执行了不会受到另一个脚本的影响.所以整体来说它的执行速度比_1.js的快很多.
async和defer的区别的另一点
- 多个defer执行顺序是有保证的,讲究先来后到,async是无保障的,谁先加载下载完就谁先执行.
这里使用两个js脚本文件来探究
// _1.js
for(let i = 0; i < 20000000000; i++) {}
console.log(1)
_2.js
console.log(2)
- 使用async
你会发现_2执行在_1之前.这个就是表明了多个async执行时候的执行顺序,谁先加载下载完谁就先执行.
- 再来看看defer
defer是讲究先来后到的规矩的,所以应该是显示_1.js执行,虽然它很慢.然后才是_2.js执行.所以输出的顺序应该显示1,然后才是2.
结果和我们预料的一样.
总结
- 首要要搞清楚 加载下载 和 执行 的区别. 浏览器碰到js文件都会先 加载和下载 js文件, 执行 js文件是要先判断是否有defer和async. 如果有defer的话,等到html解析完之后再进行执行. 如果是async, 下载加载 完之后立马 执行.
- async 和 defer的 加载下载 是异步的,不会阻塞html的解析.如果不添加defer和async的js脚本文件,它的加载下载 会阻塞html的解析.
- defer和async 执行 的特性决定了一些使用的场景,添加了async不适合用于操作DOM,如果想操作DOM,最好使用defer
- 多个async的执行顺序是不固定的,谁先下载完谁就限制性.多个defer的执行顺序是固定的,谁在前面谁就先执行
最后的最后补充一点: js的执行都会阻塞html的解析过程.这个是由浏览器的性质决定的,好像是因为 js解析和执行的线程和 UI线程是互斥的关系?