前端高频面试题——script标签中defer和async属性的区别

457 阅读6分钟

简介

HTML文档中我们会遇到三类script元素,虽然他们都是同一个标签,但是由于它们所带的不同属性,会对浏览器解析HTML的过程造成不同的影响,下面我们就针对他们的区别做一个简单的介绍:

绿色代表HTML解析阶段

灰色代表HTML停止解析阶段

蓝色代表通过网络请求加载脚本阶段

红色代表执行脚本阶段

无属性script标签 <script src='xxx'>

script标签没有任何属性时,HTML文档在解析的过程中,如果遇到了script标签,浏览器将会停止对HTML的解析并去发送网络请求加载文件,文件加载好了之后立即去执行该脚本,脚本执行完毕后浏览器才会恢复对HTML的解析

所以如果script元素放在HTML文档较前部分的话,是会阻塞HTML解析的,这也是为什么我们一般会把script标签放置在HTML文档底部的原因了,就是为了防止因为它阻塞了HTML解析而导致脚本执行的时候获取不到对应的dom元素

从下图中可以看到,浏览器一开始在正常的解析HTML,当遇到了script元素时,立马停止了对HTML的解析并开始发送请求加载对应的脚本文件,等到脚本文件加载完成之后立即开始执行,执行完毕之后才继续对HTML进行解析

当有多个无属性脚本连着出现在HTML文档中时,浏览器会保证这些脚本的加载、执行顺序都与他们在文档中出现的顺序相同

比如现在有AB两个紧挨着的无属性脚本,首先浏览器会先停止对HTML解析去请求加载A脚本,加载完之后立即执行,A脚本执行完毕后又开始HTML解析,结果又遇到了B脚本,于是停止解析,请求加载B脚本后执行。我们会发现,无属性脚本不光执行的时间按照文档中的顺序来,就连后续脚本的请求加载都要等到上一个脚本执行完毕才能开始

async脚本 <script src='xxx' async>

async具有异步的意思,与普通script标签不同的是,其请求下载脚本文件可以与解析HTML并行,也就是说浏览器可以在边下载脚本文件的同时边进行HTML的解析,但是仅仅是下载可以并行而已,脚本的执行是不能够和解析HTML并行的,脚本文件会在下载好后立马执行,下载完后如果HTML还没有解析完成,则会暂停HTML的解析,等到脚本执行完才会恢复对HTML的解析

但是如果脚本文件下载完成之前,HTML已经解析完成了,则该脚本的整个加载执行过程并不会阻塞HTML解析

从下图可以看到,浏览器一开始在正常解析HTML,当遇到了script元素时,并不会立马停止对HTML的解析,而是边发送请求下载脚本文件,边进行HTML的解析,等到脚本文件下载好之后,立马停止对HTML的解析并开始执行脚本文件,等到脚本文件执行完了之后,才继续对HTML进行解析

当有多个async脚本紧挨着出现在HTML文档中时,哪一个脚本文件先下载完就优先执行哪一个,所以async脚本文件的执行顺序并不一定是脚本出现在文档中的顺序。这里注意async脚本的请求下载并不需要等到上一个脚本请求下载完成之后才能开始,举个例子

比如AB两个连着的async脚本,A脚本请求下载并不会暂停HTML的解析过程,所以解析过程中识别到了B这个脚本,于是也开始请求下载B这个脚本,如果B这个脚本先下载完成,那么浏览器就会停止HTML解析并执行B脚本,即使B脚本的出现顺序排在A脚本后面,等到A脚本请求加载完成且B脚本执行完毕之后,浏览器才会执行A脚本

defer脚本 <script src='xxx' defer>

defer脚本也可以像async脚本一样,让脚本请求下载和HTML解析并行,但和async脚本最大的区别就是脚本文件执行的时间不同,具有defer属性的脚本会在脚本下载完并且HTML也解析完之后才执行,而具有async属性的脚本则是在文件一下载完就执行,这是很明显的一个区别

从下图中可以看到,浏览器一开始在正常解析HTML,当遇到了script元素时,并不会立马停止对HTML的解析,而是边发送请求下载脚本文件边进行HTML的解析,当文件下载完了之后,脚本文件并不会立马执行,而是会等待HTML解析完之后才开始执行刚刚下载好的脚本文件

当有多个defer脚本连着出现在HTML文档中时,浏览器会保证这些脚本的执行顺序与他们在文档中出现的顺序相同,这一点跟无属性脚本一样,不同的就是defer脚本的执行时间一定是在HTML解析完之后,所以它并不会阻塞HTML解析

当有多个连续的defer脚本插入到HTML文档中时,因为脚本的请求加载是与HTML解析并行的,所以自然和async脚本一样,下一个脚本的加载不需要等到上一个脚本加载完成,只要HTML解析到了脚本之后就可以请求加载了,但和async脚本有一个很大的区别,无论这些脚本加载完成顺序如何,最终脚本的执行顺序一定会按照它们在文档中的顺序

在什么场景下用什么类型的脚本呢?

  • 如果脚本是模块化的并且不依赖任何脚本,那么使用async脚本
  • 如果脚本依赖另一个脚本或被另一个脚本依赖,则使用defer脚本
  • 如果脚本很小并且被另一个async脚本所依赖,那么可以将无属性的脚本放置在async脚本之上
    • 因为这样我们可以确保async脚本一定在无属性脚本之后执行
  • 一般来说我们的脚本都要放置在文档的底部,因为考虑到无属性脚本和async脚本都有可能会阻塞HTML的解析,当然defer脚本是可以随便放的

兼容性

IE9及以下版本在其实现中存在一些非常糟糕的错误,defer脚本因此无法保证其执行顺序与脚本出现的顺序一致。如果您需要支持 <= IE9,我建议您根本不要使用defer,如果执行顺序很重要,请去使用无属性脚本

总结

  • 无属性script脚本只要没有放在HTML文档的底部,其一定会阻塞HTML的解析
  • 多个连续的无属性脚本请求加载和执行顺序与出现在文档中的顺序相同,而且一个脚本的加载请求一定是在上一个脚本执行完之后才开始的
  • async脚本不一定会阻塞HTML的解析,关键看async脚本下载完之前HTML是否已经完成了解析
  • 多个连续的async脚本执行顺序并不一定和文档中的出现顺序相同,其与脚本的请求加载成功顺序有关
  • defer脚本不会阻塞HTML的解析,即使该脚本请求加载可以和HTML解析并行,但是脚本的执行会被安排在HTML解析完成后之后
  • 多个连续的defer脚本一定按照文档中的顺序执行

文章标题答案

相信你理解了上述三类script标签及其区别之后,标题所对应的问题也就迎刃而解了

相同之处script标签的deferasync属性都是去异步加载外部的JS脚本文件,加载的过程中不会阻塞浏览器对HTML的解析过程(注意:这里说的仅仅是加载的过程

不同之处

  • 执行是否会阻塞HTML解析defer属性的script标签在执行的过程中一定不会阻塞HTML的解析,因为它执行的时刻一定是在HTML解析完全之后;async属性的script标签可能会阻塞HTML的解析,具体得看async脚本加载完成时HTML文档是否被解析完全,如果还没被解析完全,则会阻塞HTML的解析去执行脚本,如果HTML已经被解析完全了,执行脚本的时候也就没有机会阻塞HTML解析了

  • 执行顺序:多个async属性的script标签,不可以保证最后的执行顺序一定按照HTML文档中的顺序来;多个deferscript标签,一定会按照文档中的顺序执行

参考

juejin.cn/post/689462…

www.growingwiththeweb.com/2014/02/asy…