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 的解析。过程如下图所视:
可以看见如果请求 JS 脚本和执行 JS 代码的时间过长,script 就会阻塞浏览器对 HTML 的解析,即阻塞 DOM tree
的生成,用户就无法在页面中看见内容。
async script
当解析 HTML 过程中遇到带有 async 的 script 标签时,此时请求该脚本的网络请求是异步的,不会阻塞 HTML 的解析,会一边下载该脚本,一边解析 HTML,当脚本下载完毕后立刻执行脚本中的代码,如果此时 HTML 没有解析完就会暂停解析,过程如下图:
如果在请求脚本时 HTML 已经解析完毕,那么请求回来之后会立刻执行脚本代码:
所以 async 是不可控的,因为执行时间不确定,你如果在异步 JS 脚本中获取某个 DOM 元素,有可能获取到也有可能获取不到,因为代码的执行可能是在标签解析完毕之前,也可能是之后。而且如果存在多个 async 的时候,它们之间的执行顺序也不确定,完全依赖于网络传输结果,谁先请求到执行谁。
defer script
当解析 HTML 过程中遇到带有 defer 的 script 标签时,此时请求该脚本的网络请求也是异步的,会一边请求一边解析 HTML,如果请求完毕之后会一直等到 HTML 解析完毕后再执行 JS 代码,过程如下图:
最后,根据上面的分析,不同类型 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
属性是防范这种问题的一个武器。