乍眼一看,script标签的async和defer属性的作用好像是一样的,但其实它们之间存在着一些细微差别,更多的场景下你需要的其实是defer属性,而非async属性,即使async属性看上去好像更性感一些嘿嘿。
在 async 和 defer 属性出现之前
默认情况下,当浏览器在加载DOM元素的时候碰到script标签,就会被迫停止加载页面剩余的DOM元素,直到script标签里的代码下载并执行结束。这意味着script标签里的代码是阻塞性的,会阻止页面DOM元素的下载和渲染。如果script标签里的代码需要执行很长的时间,这意味着页面将会有很长一段时间处于空白的状态,这体验也太差了吧。
在async和defer属性出现之前,聪明的程序员们想出来的办法是将script标签置于页面底部(即</body> 标签前面),这样就不会阻塞页面的渲染了。这种方法依然有效,你仍然可以自由使用它,并不需要为继续使用它而感到惭愧。
async VS defer
async和defer属性都可以在不阻塞页面加载的情况下下载script标签里的代码,但它们之间是存在区别的。
-
defer属性会等待页面DOM元素加载完再执行,而async属性不会;这意味着当你的
script代码需要DOM元素的时候,你就应该使用defer属性;如果你固执己见仍要选用async属性,你将面临获取不到页面DOM元素的风险。
<head>
<script src="defer.js" defer></script>
<script src="async.js" async></script>
</head>
<body>
<!-- 20000 buttons -->
</body>
// defer.js
const deferButtons = document.querySelectorAll('button')
console.log('Defer:', deferButtons.length)
// 20000
// async.js
const asyncButtons = document.querySelectorAll('button')
console.log('Async:', asyncButtons.length)
// 11139
-
defer属性标记的 script 标签按加载顺序,而async属性不会;这意味着当你的脚本文件之间需要互相依赖的时候,可预测的脚本执行顺序是必须的。
<script async src="script1.js"></script>
<script async src="script2.js"></script>
<script async src="script3.js"></script>
// two
// three
// one
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>
<script defer src="script3.js"></script>
// one
// two
// three
你什么时候可以使用 async
- 当你的脚本代码所需要的
DOM元素已经全部加载完或者你根本就不需要DOM元素; - 当你的脚本文件不需要依赖其他脚本文件的时候。
你什么时候可以使用 defer
在那些不适合使用async属性的场景下,都能使用defer属性。
使用defer属性具有如下好处:
- 它会在不阻塞页面加载
DOM元素的情况下异步下载,减少页面总体载入资源的时间; - 它下载完之后会等待页面所有
DOM元素都渲染完成才执行,所以你不用担心获取不到所需DOM的元素; - 它会按顺序执行页面上所有的
script文件,所以你可以将需要优先执行的script文件放在首要位置。
鉴于defer属性具有上述好处,你甚至可以将script标签放置在页面头部,也绝不会阻塞DOM元素的加载。 将script标签放置在页面头部,对页面所需要的脚本文件一目了然,整体上创建了一个更加干净的HTML。
<head>
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>
<script defer src="script3.js"></script>
</head>
总结
虽然async属性看上去好像更性感一些,但是大多数场景下,你其实更加需要defer属性。而且更加推荐使用defer属性,因为相对于传统的script代码的加载方式,使用defer异步加载,可以减少页面总体载入资源的时间。