脚本标记详解
这是我参与更文挑战的第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>
标签中,这样做的好处是能够将 CSS
和 JS
都集中放到一起,但是会带来一个问题,页面要等到所有的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>
总结
下面对本文的内容做个简单的总结:
-
<script>
会阻塞HTML
的解析,通常要放在<body>
内容的最后; - 可以使用
defer
属性提前加载外部脚本,但推迟到文档解析完后再执行,执行顺序与标签次序一致; - 可以使用
async
属性实现外部脚本加载过程中不阻塞页面解析,加载完成后停止页面解析并执行代码,执行顺序取决于加载完成的顺序; - 模块脚本的加载执行顺序与
<script defer>
一致; -
async
属性适用于内联的模块脚本。
提问
如果是动态创建的脚本,它的加载执行顺序是怎么样的呢?
let script = document.createElement('script');
script.src = "example.js";
document.body.append(script);
上一篇: HTML - 脚本标签异步加载
下一篇: 一文读懂脚本标签