编程崽

登录

一叶在编程苦海沉沦的扁舟之上,我是那只激情自射的崽

script 标签的异步属性

script 标签的异步属性

前端开发页面时,都知道需要把 js 放到页面最下面,以期不影响页面展示、缩短白屏时间。

HTML 的 script 标签,是提供了两个属性,来配置 js 的阻塞影响、执行时机的。

使用了这两个属性,就更好的控制 script 引入的 js 对页面的加载时影响了。

问题复现

html 在加载 js 时,是会阻塞后面的 html 加载和执行的。

比如如下代码,执行顺序如下:

  • 先下载这个 html,下载完成后,开始同时加载里面的这 4 个 js 文件(同时下载同一域名下的文件,不同浏览器的数量限制不同,一般是6~20个,超过的会阻塞等待)
  • 添加 DOMContentLoaded 的事件监听,待执行「代码段 A」
  • 当 index_1 下载完成,自动执行
  • 当 index_2 下载完成,会先判断它之前的 js,是否已经加载完并执行完,如果还没有,则等待,否则它也自动执行
  • 和 index_2 同理,当 index_3、index_4 下载完成,也会先判断他们之前的 js 是否下载完成且加载完成。
  • 当 index_2 加载且执行完成,而 index_3 还没有加载且执行完时,页面只会显示 div.box_1,而不会显示 div.box_2,看上去很怪异,但是他是正常的
  • 当所有 js 文件夹被加载和执行后,触发 DOMContentLoaded 事件,执行「代码段 A」

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。

html 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script>
    document.addEventListener('DOMContentLoaded', function(event) {
      // 代码段 A
    });
  </script>
  <script src="./index_1.js"></script>
  <script src="./index_2.js"></script>
</head>
<body>
  <div class="box_1"></div>
  <script src="./index_3.js"></script>
  <div class="box_2"></div>
</body>
<script src="./index_4.js"></script>
</html>

defer 属性

使用方法:

html 复制代码
<script defer src="./index_3.js"></script>

defer 属性只对 src 引入的 js 生效

有 defer 属性的脚本,它的下载机制不变,还是会和其他脚本一起现在,但不会阻塞文档加载,也厚是它后面的元素和 js,不必等它下载完并执行完才能加载了。

如果上以案例中的 index_3.js 添加了 defer,即使 index_3.js 尚未加载完,它后面的元素和 js 也会正常展示和加载。

当有 defer 属性的脚本下载完成后,会先不执行,等到 Dom 是加载解析、DOMContentLoaded 事件之前,才会执行。

当然,如果有 defer 属性的脚本文件太大,DOMContentLoaded 也会暂不执行,直到这个 js 下载完、执行完后再执行。

综上所述,有 defer 属性的脚本,会和其他 js 文件一起下载,不会阻塞正常文档流,但会阻塞 DOMContentLoaded,他会等正常文档流加载完、即将执行 DOMContentLoaded时才执行,执行完成后,再执行 DOMContentLoaded。

所以,一些包含 dom 操作的 js,可以添加 defer 属性。

H5 的 async 属性

使用方法:

html 复制代码
<script async src="./index_3.js"></script>

async 属性只对 src 引入的 js 生效

H5 新添的 script 标签的属性:async。

对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。

有 async 属性的脚本,会尽快开始下载,并尽快开始执行,即使其前面的 js 和 文档流还没有加载执行完也不影响,同时也不会阻塞其后面的 js 和 文档流,相当于完全脱离了文档流限制。

和下面的代码有一样的效果。

js 复制代码
var script = document.createElement('script');
script.src = "./file.js";
document.querySelector('body').appendChild(script);