script 标签详解

·  阅读 287
script 标签详解

这是我参与更文挑战的第5天,活动详情查看:更文挑战

关于<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属性一样。

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>
复制代码

模块脚本加载顺序

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); 
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改