JavaScript 文件加载执行顺序

关于<script>标签

众所周知,<script>标签是用于将JavaScript代码插入到HTML的主要方法。它具有内联和外部形式两种使用方式。

内联代码是将JavaScript代码直接写在标签里,外部形式则是通过标签的src属性引入外部的JavaScript文件。当<script>标签具有src属性的时候,标签内的代码会被忽略,如下所示:

<!DOCTYPE html>
<html>
<head>
  <title>Example HTML Page</title>
  <!--内联脚本-->
  <script>
    alert('Hello World!');
  </script>
  <!--外置脚本-->
  <script src="file.js">
    alert(1); // 此内容会被忽略,因为设定了 src
  </script>
</head>

<body>
  <!-- 页面内容 -->
</body>
</html>

<script> 标签的加载顺序

刚刚的代码里,我们将<script>放在了<head>标签中,这样做的好处是能够将 CSSJS 都集中放到一起,但是会带来一个问题,页面要等到所有的JavaScript代码都下载、解析和解释完成之后才会继续渲染,带来的直观感受就是页面加载变慢了。

我们为了提升用户体验,让页面加载快一点,通常会把<script>标签放在<body>标签的底部,如下所示:

<!DOCTYPE html>
<html>
<head>
  <title>Example HTML Page</title>
</head>

<body>
  <!-- 页面内容 -->
  <script>
    alert('Hello World!');
  </script>
  <script src="file.js"></script>
</body>
</html>

这样,浏览器会在页面渲染完成后再加载和执行JavaScript代码,让用户感觉到页面加载更快。

那么有没有办法让<script>标签放在<head>标签里,但是在解析完HTML之后再执行呢?

有。<script>标签有个属性叫defer,翻译成中文就是推迟的意思。这个属性++只能用在外部文件形式的<script>标签中++,这样该标签引入的JavaScript代码能够提前加载,但是会延迟到整个页面文档都解析完毕之后再运行。如果有多个带有defer属性的<script>标签,它们会按照顺序进行执行。

<!DOCTYPE html>
<html>
<head>
  <title>Example HTML Page</title>
  <script defer src="exampleOne.js" ></script>
  <script defer src="exampleTwo.js" ></script>
</head>

<body>
  <!-- 页面内容 -->
  <!-- 页面渲染完成后执行 exampleOne.js 的代码,再执行 examplTwo.js 的代码 -->
</body>
</html>

现在我们知道了放在<head>里的JavaScript代码下载和执行会阻塞页面渲染,而带有defer<script>会在页面渲染完成后才执行。那如果我们希望引入的外部JavaScript在下载的时候,页面能够继续渲染,下载完成后停止页面渲染执行JavaScript代码的话,该怎么实现呢?

这里我们学习<script>标签的另一个属性:async,翻译过来就是异步的意思,该属性同样也++只适用于外部脚本++。带有该属性的JavaScript代码在下载的时候不会阻塞页面的渲染,与defer属性一样。不同的是当脚本文件下载完成后,浏览器会立即停止页面渲染并执行JavaScript代码。如果有多个带有async<script>,它们的执行顺序与出现的次序无关,哪个JavaScript代码先下载完就先执行哪个。

<!DOCTYPE html>
<html>
<head>
  <title>Example HTML Page</title>
  <script async src="exampleOne.js" ></script>
  <script async src="exampleTwo.js" ></script>
</head>

<body>
  <!-- 页面内容 -->
  <!-- 渲染过程中停下页面渲染执行 JS 代码,执行顺序不一定 -->
</body>
</html>

通过下图看看不同情况下<script>的加载顺序,绿色代表HTML解析,灰色代表HTML停止解析,蓝色代表JavaScript代码下载,粉色代表JavaScript代码执行。
image

模块脚本加载顺序

ES6引入了模块规范之后,带有type="module"属性的<script>标签会告诉浏览器相关代码应该作为模块执行。

<script type="module">
  //模块代码
</script>
<!-- 引入外部模块代码 -->
<script type = "module" src="module.js"></script>

模块脚本的加载规则与<script defer>的加载规则一致。当HTML解析到<script type="module">标签后会立即下载模块文件,然后延迟到文档解析完成之后才会执行相应代码。

那如果在模块脚本里加上async属性会怎么样?

带有 async 属性的模块脚本会按照正常异步脚本的方式进行加载,即当HTML解析到<script async type="module">标签后会立即加载模块文件,但是不阻塞HTML解析,当加载完成后立即停止HTML解析并运行相应代码。

另外,在上文中有提到async属性只适用于外部脚本,这是相对于非模块脚本而言的。对于模块脚本,async属性也适用于内联脚本。

<!-- 所有依赖都获取完成(analytics.js)然后脚本开始运行 -->
<!-- 不会等待 HTML 文档或者其他 <script> 标签 -->
<script async type="module">
  import {counter} from './analytics.js';
  counter.count();
</script>

总结

下面对本文的内容做个简单的总结:

  1. <script> 会阻塞HTML的解析,通常要放在<body>内容的最后;
  2. 可以使用defer属性提前加载外部脚本,但推迟到文档解析完后再执行,执行顺序与标签次序一致;
  3. 可以使用async属性实现外部脚本加载过程中不阻塞页面解析,加载完成后停止页面解析并执行代码,执行顺序取决于加载完成的顺序;
  4. 模块脚本的加载执行顺序与<script defer>一致;
  5. async属性适用于内联的模块脚本。

提问

如果是动态创建的脚本,它的加载执行顺序是怎么样的呢?

let script = document.createElement('script');
script.src = "example.js";
document.body.append(script); 

最后

更多<script>相关内容可以翻阅《现代 JavaScript 教程》和《JavaScript 高级程序设计(第4版)》进行查看。

关注我的公众号『玩点前端』,回复『前端』 可以获得相关学习资料;回复『script标签』可以获取提问环节的问题答案。

在这里插入图片描述

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值