在 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)