script标签中的defer和async有什么区别

在HTML中,我们可以使用<script>标签和它的src属性来引入一个外部脚本,例如:

<script src="scripts/main.js"></script>

 其中src属性指向的是需要加载的JavaScript文件路径。

这样做的好处是不用将JS脚本代码直接写入到HTML文件中,可以区别管理HTML代码和JS代码,可读性更高,也更利于维护。

更重要的是:如果一个JavaScript文件被多个页面共享,那它只会被第一个使用它的页面下载一次,后续页面可以从浏览器缓存中获取该文件,这样也能够提升加载效率。

这种方式虽好,但仍有些不足。

比如:页面上的HTML和JS代码都是按照从上至下的顺序执行的,如果遇到某个JS文件下载和执行的时间过长,就会阻塞剩余的页面加载,导致页面加载时间过长。

再比如:如果想用JS代码操作页面上的元素,但JS代码的加载早于要操作的HTML元素,代码将会出错。

那么,对于一些不会影响页面渲染的功能性JS代码,我们是不是可以让它晚点加载,以提高执行效率呢?

对执行顺序有要求的JS代码,我们是不是可以让它延后加载呢?

答案是肯定的!

旧办法是把需要延后加载的脚本元素放在文档底端的</body>标签之前,这样脚本就可以在HTML解析完毕后加载了。

还有个比较新的方法是在<script>标签内加入defer,例如:

<script src="scripts/main.js" defer></script>

这种写法和把脚本放在文档底端的</body>标签之前具有同样的效果,可以让代码在文档完成解析后,触发DOMContentLoaded事件前执行。

注意:如果存在多个包含defer的JS脚本,它们会在触发DOMContentLoaded事件前,按照出现的先后顺序依次执行。

这种方法适合页面较小,JS脚本较少,或者需要预加载其他页面脚本的情况。

但是对于有大量JS代码的大型网站,让JS代码在文档完成解析后才执行会带来显著的性能损耗。

比如页面看起来已经渲染完毕,但是点击页面上的功能按钮并没有反应,就有可能是实现按钮功能的JS代码执行的太晚,而用户在代码还未执行完毕时就产生了操作。

对于这种需要在减少阻塞的基础上尽快执行的代码,我们可以选择另一个折中的方法:

<script src="scripts/main.js" async></script>

通过在<script>标签内加入async,可以使脚本在下载阶段不阻塞其余代码的解析,并在下载完成后尽快执行。

也正是因为如此,如果存在多个包含async的JS脚本,它们会按照下载完成的顺序执行(哪个先下载完就先执行哪个),这样脚本的运行次序就无法控制。

一般来说:当页面的脚本之间彼此独立,且不依赖于本页面的其它任何脚本时,async是最理想的选择。

最后做个总结:

  • deferasync都能消除脚本下载期间造成的阻塞,只是执行的时机不同。
  • async会在脚本下载完成后尽快执行,如果脚本无依赖,不用考虑执行顺序,那么应该使用async
  • defer会在脚本下载完成且页面渲染完毕后执行,如果脚本依赖于其它脚本,需要考虑执行顺序,则应使用defer,将关联的脚本按顺序置于HTML中。