跳转到内容

script 标签你真的用明白了吗?

<script>标签不就是写 JavaScript 代码的地方吗?

没错!可是你知道它的asyncdefer属性是用来干嘛的吗?crossorigin属性有什么用?为什么 Vite 项目的入口文件中要使用type="module"

来嘛,我们一个一个的来看!

deferasync

首先我们要知道浏览器在解析html文档的时候,是自上而下,一边解析一边渲染的。遇到CSS样式和普通的script标签,会阻塞解析过程,直到样式脚本下载并执行完成后,才会继续往下解析。

因此,我们通常都是将js脚本放到body的最后面,以避免阻塞html文档的解析与渲染(实际上也阻塞了,因为脚本本身也属于解析html文档的一部分)。

html
<!-- 省略 -->
    <!-- 脚本文件放在body底部 -->
    <script src="js/script1.js"></script>
    <script src="js/script2.js"></script>
  </body>
</html>

deferasync属性就可以避免阻塞html文档的解析与渲染,无论<script>标签处于什么位置。

在解析的过程中,如果遇到有deferasync属性的<script>标签,浏览器会异步下载脚本文件,页面继续解析。

  • 如果是async属性,则会在下载完成后立即执行,且执行会阻塞页面解析
  • 如果是defer属性,会等到页面全部解析完成后,DOMContentLoaded事件之前按顺序执行。也就是说,它会阻塞DOMContentLoaded事件,直到脚本下载并执行完成。
html
<!-- 可以放在任意位置 -->
<!-- 1.异步下载并立即执行 2. 不能确定执行顺序 -->
<script async src="js/script1.js"></script>
<script async src="js/script2.js"></script>

<!-- 异步下载DOMContentLoaded事件之前顺序执行 -->
<script defer src="js/script1.js"></script>
<script defer src="js/script2.js"></script>

根据上面的介绍,async中的脚本不能操作DOM元素,也不能依赖其他的脚本文件。其使用场景一般用于独立、不依赖DOM的脚本,如:广告、统计分析脚本,也可用于动态加载的脚本。

defer则更加常用,它可以完全代替将脚本放在body底部的传统写法。并且性能更优,因为它是异步下载,节省了下载的时间,对前端首页性能优化有一定的作用。

思考:defer的方式与将脚本放在body底部,两者有什么区别?

crossorigin

从名称上就可以看出,只有在访问跨域资源时才会用到。

我们知道,<script>标签是可以访问跨域资源的,早期,解决跨域问题的JSONP就是利用了此特性。

但是,跨域资源的访问也存在许多的安全问题,现在一些浏览器对跨域的脚本资源也做了一些限制。

crossorigin会启用浏览器的CORS检查,如果不通过,浏览器就会拒绝执行此代码。当然,这需要服务端的支持,现在很多CDN平台都已支持CORS响应头

思考:既然不配置crossorigin就能访问跨域资源,那为什么还要手动配置crossorigin属性呢?这不是多此一举吗?

在实际开发中,我们可能会遇到这样的代码:

html
<script src="xxxxx" integrity="oqVuA..." crossorigin="anonymous"></script>

此时,crossorigin可以配合integrity属性进行资源完整性校验,如果资源被篡改过,浏览器就会拒绝执行。

另外,使用crossorigin属性后,如果脚本代码报错,能够获取到详细的错误信息

还有,如果你的网站配置了CSP内容安全策略,那么就必须使用crossorigin属性以启用CORS来加载资源。

html
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://trusted-cdn.com">
<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp">

总之,使用crossorigin是安全性上的一个提升,如果访问第三方的资源,建议使用。

crossorigin属性有两个值:

  • anonymous:启用CORS,如果省略或使用非法值,默认使用anonymous
  • use-credentials:启用CORS,并且携带cookie信息

type属性

很久以前,我们常用的方式是type="text/javascript",但现在这种写法通常都被省略了,因为目前浏览器已经明确规定了js脚本只能使用text/javascript类型。

现在我们主要介绍另外两个属性值:moduleimportmap

很多人接触这两个属性值可能都是源于 Vite 和 Vue,在webpack时代,我们通常没有使用过浏览器的ES模块语法,而 Vite 正是利用了这一特性,才让我们在开发环境的编译速度变得非常快。

通俗来说,只要设置了type="module",浏览器就能识别我们代码中的 import 语法。

html
<div id="app">{{ message }}</div>

<script type="module">
  import { createApp, ref } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'

  createApp({
    setup() {
      const message = ref('Hello Vue!')
      return {
        message
      }
    }
  }).mount('#app')
</script>

对于type="importmap"我们可能比较陌生一点,如果我们想直接写import { createApp, ref } from 'vue',则需要先导入映射表,来指定导入vue的目标地址。

html
<script type="importmap">
  {
    "imports": {
      "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
    }
  }
</script>

通过这些代码我们可以一目了然的明白它们的用法。

但是使用浏览器的ES模块能力,需要考虑浏览器的兼容性,尽量避免在生产环境使用。

需要注意的是,启用浏览器的ES模块后,脚本会使用CORS方式加载,并且是异步加载,与defer效果类似,并且自动使用严格模式

最后

如果你认真看完了,你会发现文章对crossorigin安全方面的介绍并不深入。比如:<script>标签上的nonce属性又是什么?

确实,这涉及到安全领域相关的一个话题,要深入讲解确实很难,一方面是我自己的能力有限,另一方面因为与文章主题不符。

后期我会专门写一篇关于CSP安全策略的文章,有兴趣的朋友点赞支持一下吧!