浏览器在解析 HTML 的时候,如果遇到一个没有任何属性的 script 标签,就会暂停 HTML 解析,先发送网络请求获取该 JS 脚本的代码内容,然后让 JS 引擎执行该代码,当代码执行完毕后恢复解析。可以使用 async 或者 defer 取消阻塞 HTML 解析。

<script>

如果 script 标签什么属性也没有带,那么浏览器解析到这个标签时,会首先去下载脚本(带src属性)并暂停 HTML 解析,下载完成后执行脚本,待执行脚本结束之后,浏览器会恢复 HTML 解析,这之间就会消耗时间,如果这个标签放在body上面,就会影响页面解析,产生浏览器白屏,所以这就是 script 标签尽可能放在 body 最后的原因。

解析过程如图所示:

<script async>

当浏览器遇到带有 async 属性的 script 时,浏览器会异步去下载该脚本,不会阻塞浏览器解析 HTML。

之后会存在两种情况:

  • 脚本下载完成后,如果此时 HTML 还没有解析完,浏览器会暂停解析,先让 JS 引擎执行代码,执行完毕后再进行解析。
  • 脚本下载完成后,如果此时 HTML 已经解析完成,那会直接执行这个脚本代码。

async 属性的 script 脚本执行没有顺序,谁先下载完成谁先执行,依赖于网络传输等情况。

解析过程如图所示:

<script defer>

当浏览器遇到带有 defer 属性的 script 时,浏览器会异步去下载该脚本,不会阻塞浏览器解析 HTML。

之后存在两种情况:

  • 脚本下载完成后,如果此时 HTML 还没有解析完,浏览器会继续解析 HTML,等待解析完毕后,执行脚本。
  • 脚本下载完成后,如果此时 HTML 已经解析完成,那会直接执行这个脚本代码。

defer 属性会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。多个 defer 属性脚本会按顺序执行脚本。

解析过程如图所示:

区别总结

相同点:

  • 异步加载脚本
  • 不阻塞 HTML 解析

不同点:

  • defer 是在 HTML 解析完成后,DOMContentLoaded之前执行; async 是在下载完成后立即执行,不论 HTML 是否解析完毕。
  • 多个 defer 按照顺序执行; 多个async 执行顺序不可控,和网络请求返回顺序有关。

不同点总结为一个表格:

script标签 JS执行顺序 是否阻塞HTML解析
<script> 在 HTML 中的顺序 阻塞
<script async > 网络请求返回顺序 可能阻塞,也可能不阻塞
<script defer > 在 HTML 中的顺序 不阻塞