Skip to main content

script标签详解

将 JS 代码插入 HTML 中,主要通过 script 元素,其有下面8个属性。

属性描述
src表示包含要执行的代码的外部文件
type表示代码块中脚本语言的内容类型,默认取值为 "text/javascript",如果取值为 "module",则代表是一个 ES6 模块,此时使用 import 和 export 语法才不会报错
async异步请求脚本,请求完毕后立刻执行 JS 代码
defer异步请求脚本,请求完毕后等到 HTML 解析完才执行 JS 代码
crossorigin配置相关请求的CORS(跨源资源共享)设置
integrity允许比对接收到的资源和指定的加密签名以验证子资源完整性

async 和 defer 详解

浏览器在解析 HTML 过程中,如果遇到 script 标签,一般情况下会暂停 HTML 的解析,此时会发送一个网络请求下载该 JS 脚本的代码内容,并且让 JS 引擎执行该脚本代码,在这之后再进行剩余 HTML 的解析。过程如下图所视:

script

可以看见如果请求 JS 脚本和执行 JS 代码的时间过长,script 就会阻塞浏览器对 HTML 的解析,即阻塞 DOM tree 的生成,用户就无法在页面中看见内容。

async script

当解析 HTML 过程中遇到带有 async 的 script 标签时,此时请求该脚本的网络请求是异步的,不会阻塞 HTML 的解析,会一边下载该脚本,一边解析 HTML,当脚本下载完毕后立刻执行脚本中的代码,如果此时 HTML 没有解析完就会暂停解析,过程如下图:

script

如果在请求脚本时 HTML 已经解析完毕,那么请求回来之后会立刻执行脚本代码:

defer2

所以 async 是不可控的,因为执行时间不确定,你如果在异步 JS 脚本中获取某个 DOM 元素,有可能获取到也有可能获取不到,因为代码的执行可能是在标签解析完毕之前,也可能是之后。而且如果存在多个 async 的时候,它们之间的执行顺序也不确定,完全依赖于网络传输结果,谁先请求到执行谁。

defer script

当解析 HTML 过程中遇到带有 defer 的 script 标签时,此时请求该脚本的网络请求也是异步的,会一边请求一边解析 HTML,如果请求完毕之后会一直等到 HTML 解析完毕后再执行 JS 代码,过程如下图:

script

最后,根据上面的分析,不同类型 script 的执行顺序及其是否阻塞解析 HTML 总结如下:

script 标签JS 执行顺序是否阻塞解析 HTML
<script>在 HTML 中的顺序阻塞
<script async>网络请求返回顺序可能阻塞,也可能不阻塞
<script defer>在 HTML 中的顺序不阻塞

Tips:

  • 这里说的解析 HTML,我的理解就是构建 DOM 树的过程
  • defer 和 async 只用于外部脚本

使用 script 的两种方式

直接在 script 内写 JS 代码:

<script>
function sayHi() {
console.log("Hi!");
}
</script>

使用 src 属性,指向包含 JS 代码的外部文件 url:

<script src="example.js"></script>

另外,使用了src属性的<script>元素不应该再在<script></script>标签中再包含其他JavaScript代码。如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。

src 指向外部域

script 标签的 src 可以包含来自外部域的 JavaScript 文件。跟<img>元素很像,<script>元素的src属性可以是一个完整的URL,而且这个URL指向的资源可以跟包含它的HTML页面不在同一个域中,比如这个例子:

<script src="http://www.somewhere.com/afile.js"></script>

浏览器在解析这个资源时,会向 src 属性指定的路径发送一个 GET 请求,以取得相应资源,这个初始的请求不受浏览器同源策略限制。

这个能力可以让我们通过不同的域分发 JavaScript。不过,引用了放在别人服务器上的 JavaScript 文件时要格外小心,因为恶意的程序员随时可能替换这个文件。在包含外部域的 JavaScript 文件时,要确保该域是自己所有的,或者该域是一个可信的来源。<script>标签的integrity属性是防范这种问题的一个武器。