我们知道,html 在解析过程中,如果遇到外部的脚本,会暂停当前页面的解析过程,而是去下载这个引入的外部脚本,然后执行这个外部脚本,这样无疑会导致当前的页面的渲染被阻塞,这也是为什么建议将脚本文件的加载放在文档的最后。
script 标签新增加了两个布尔属性 defer
和 async
用于控制脚本的加载和解析,这两个属性的目的都是为了保证引入的外部的脚本不会阻塞当前页面的渲染过程。
<script async src="script.js"></script> |
其中:
defer
指定了脚本在页面在渲染过程中并行加载,但是直到文档解析完成后,DOMContentLoaded
事件触发前执行,效果类似于把 script 标签放在了文档的底部(</body>
前)。async
指定了脚本在页面在渲染过程中并行加载,但是和 defer 不同的是,当脚本加载完完成后会立即执行,此时如果页面的还未渲染完成,则渲染过程会被打断。
注意:动态插入的脚本 (如使用 document.createElement
) 默认是异步执行的(async=true
),如果想要其同步执行,需要加上 async=false
。
相同点与不同点
这两个属性有以下共同点:
- 它们在加载的时候不会阻塞当前页面的渲染过程。
- 它们只对引入的外部脚本生效,对内联脚本不生效。
- 它们是页面渲染中并行加载脚本的,在脚本加载过程中不会打断页面的渲染过程。
document.write
会被忽略,并在控制台上输出类似于这样的错误信息"A call to "document.write()" from an asynchronously-loaded external script was ignored"
。
它们的区别在于:
- 如果有多个
defer
脚本,defer
脚本的执行是按照defer
脚本的书写顺序执行的。如果有多个async
脚本,async
脚本的执行时按照async
脚本的加载完成的先后顺序执行的,也就是说先加载完的脚本先执行。 defer
脚本不会大段渲染过程,而async
脚本会打断渲染过程。- 两个属性同时使用的时候会忽略
defer
采用async
模式。 defer
会在DOMContentLoaded
事件触发前执行,而async
则会在load
事件之前执行,但并不能确保与DOMContentLoaded
的执行先后顺序。
如下图,其实已经明显说明了它们的差异:
总结
使用这两个属性,可以将脚本的加载放到页面的前面,以便尽可能快的加载完脚本,但是也要根据情况判断使用哪个属性,不能生搬硬套:
比如,如果浏览器不支持这两个属性,放到页面前面加载反而起了负优化的作用。
比如,如果有多个项目依赖的脚本,使用 async
异步加载可能会导致错误,使用 defer
比较好。但是并不是说 async
就没有应用场景了, async
适用于加载不依赖 DOM 的脚本,或者一些独立的小文件,此时我们并不关心文件的执行位置,只需要保证尽快加载完毕,比如加载 GA。