js脚本中的async和defer详解
本文的知识点都是从英文官方文档总结而来。若有不当的地方,请在评论区指出,谢谢。
前提知识
DOMContentLoaded事件的触发条件:当纯HTML全部加载并解析完毕时触发DOMContentLoaded事件,而不必等待样式表、图片或者子框架。
load事件的触发条件:整个页面及所有依赖资源(如样式表和图片)都加载完成时触发load事件。该事件不可取消,也不会冒泡。
三个小场景
场景一
<html>
<head>
<meta charset="utf-8">
<title>Skeleton example</title>
<!-- 这个脚本叫“外部”脚本-->
<script src="script.js"></script>
</head>
<body>
<div>
我被上面的外部脚本阻塞了,浏览器会先加载外部脚本并运行这个外部脚本,然后才会解析我这个dom。
</div>
</body>
</html>
上面的外部js脚本会阻塞其后面的html代码,浏览器会立即加载js脚本并运行,然后才会继续向下解析和渲染dom。
场景二
<script async src="js/vendor/jquery.js"></script>
<script async src="js/script2.js"></script>
<script async src="js/script3.js"></script>
上面三者的调用顺序是不确定的。jquery.js
可能在 script2
和 script3
之前或之后调用,如果这样,后两个脚本中依赖 jquery
的函数将产生错误,因为脚本运行时 jquery
尚未加载。
场景三
<script defer src="js/vendor/jquery.js"></script>
<script defer src="js/script2.js"></script>
<script defer src="js/script3.js"></script>
上面三者的调用顺序是按照书写顺序
加载。添加 defer
属性的脚本将按照脚本在页面中出现的顺序加载,因此上面这个示例可确保 jquery.js
必定加载于 script2.js
和 script3.js
之前,同时 script2.js
必定加载于 script3.js
之前。
总结
- 1、
async
和defer
属性都仅适用于外部脚本,在内联脚本中不生效。 - 2、没有添加
async
或defer
属性的外部js脚本会阻塞html的加载和解析。 - 3、当外部js脚本添加
async
或defer
属性后,外部脚本变为异步脚本。此时,js的下载及运行
与后面的dom元素的解析和渲染
会并行进行。 - 4、当外部js脚本添加
async
属性后,js脚本变为异步脚本,js脚本不会阻塞html的解析和渲染。但是一旦js脚本的网络请求完毕,如果此时html还没解析完毕,则浏览器会暂停解析html,并让js引擎执行js代码,js执行完毕后再继续解析html。此外,多个async
类型的js脚本会无序执行,谁先加载完谁先执行。 - 5、当外部js脚本添加
defer
属性后,js脚本变为异步脚本,js脚本不会阻塞html的解析和渲染。一旦js脚本的网络请求完毕后,如果此时html还没解析完毕,则浏览器会继续解析html,等html解析完毕后才去执行请求完毕的js代码。此外,多个defer
类型的外部js脚本会按照书写顺序加载。 - 6、从4和5中看出:
async
可能阻塞html的解析,也可能不阻塞;defer
不会阻塞html的解析。 - 7、
async
可能在DOMContentLoaded之前或之后执行,但一定在load事件之前执行;defer
比较特殊,这个会在文档解析后,DOMContentLoaded事件之前执行,由此可以看出当外部脚本包含defer
属性时,会阻塞DOMContentLoaded事件的触发,直到外部脚本完成加载并执行。