在 HTML 页面中引入 JS 文件有三种常见的方式:通过 script 标签直接引入、给 script 标签添加 async 属性、给 script 标签添加 defer 属性。

# 纯 script 标签

下图展示的是直接使用 script 标签引入 JS 文件。从图中可以看到,JS 文件的下载和执行都会阻塞 HTML 页面的解析。

# async

下面两幅图展示的是给 script 标签设置 async 属性的情况。从图中可以看到,设置 async 属性后,JS 的文件的下载是不会阻塞页面渲染的,但是当 JS 文件执行时,如果页面还未渲染完成,此时已然会阻塞页面的渲染。

JS 文件下载完毕后,页面渲染还没完成,JS文件执行时会阻塞页面的渲染。

JS 文件下载完毕后,页面渲染已经完成,JS 文件执行时不会阻塞页面渲染。

如果页面中存在多个设置 async 属性的 script 标签,JS 文件的执行顺序依赖于哪个 JS 文件先返回,无法保证执行顺序。

# defer

给 script 标签设置 defer 属性后,JS 的下载不会阻塞 HTML 的解析,如果 JS 文件已经下载完毕,HTML 还没有解析完毕会等到 HTML 解析完毕后再执行 JS 代码。

如果页面中存在多个设置 defer 属性的 script 标签,会按照引入顺序依次执行。

# 总结

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

对于有可能阻塞 HTML 解析的引入 JS 的方式,操作 DOM 元素时一定要小心,因为此时要操作的这个 DOM 元素可能还不存在。

参考:

《JS高级程序设计(第四版)》

async vs defer attributes (opens new window)

浅谈script标签的defer和async (opens new window)

图解 script 标签中的 async 和 defer 属性 (opens new window)