浏览器在解析 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 中的顺序 | 不阻塞 |