本文为HTML标准解读系列文章,其他文章详见这里。
在一个HTML页面中执行js脚本有很多方式,包括但不限于以下几种:
- 使用script标签执行脚本;
- 使用
javascript:URL
的导航; - 使用DOM上的事件监听机制;
- 使用svg相关技术中的脚本能力;
在这些方式中,使用最多的无疑是第一种。script标签允许开发者给页面插入js脚本,而根据type
属性的值,可以把script元素分成4种不同的类型:
类型 | 对应的type属性值 | 描述 |
---|---|---|
js传统脚本(classic script) | 没有声明type属性,或type属性值为空,或type属性值匹配任一JavaScript MIME类型(如text/javascript )。 |
以ECMAScript顶层Script语法规则进行解析的脚本。 |
js模块脚本(module script) | “module” | 以ECMAScript顶层Module语法规则进行解析的脚本。 |
Imports map | “importmap” | 控制页面内模块标识符(module specifier)的解析。 |
数据块 | 除了上述以外的其他值 | 浏览器不会被对其作处理,内部的文本可以作为数据在其他脚本中时候。 |
对于前面三种类型,可以使用HTMLScriptElement.supports(type)
的方法来检测浏览器是否支持这些类型,对应的参数type
分别是classic
、module
或importmap
。
js传统脚本
js传统脚本是我们使用最多的script类型。我们可以在标签内直接写js代码,也可以通过src属性引入一个外部的js文件。
基于历史原因,script标签内容的解析有一些奇怪的规则。比如,以下的script标签都无法按照预期运行:
<!-- 1: script标签把字符串内容</script>看成是闭合标签 -->
<script>
const example = "script的闭合标签是</script>";
console.log(example);
</script>
<!-- 2: script标签把<!--看成是注释的起始标签 -->
<script>
if (x <!--y) { ... }
</script>
为了避免这些坑,标准建议把所有script标签里的字符串、正则表达式、注释内容里面的<!--
、<script
、</script
都使用\x3C!--
、\x3Cscript
、\x3C/script
转义,并且避免在js表达式中使用这类写法。所以,以上的问题可以这么修正:
<script>
const example = "script的闭合标签是\x3C/script>";
console.log(example);
</script>
<script>
if (x < !--y) { ... }
</script>
js模块脚本
把代码拆解成不同的模块是程序员应对复杂度的一个重要手段。在js模块脚本获得浏览器原生支持之前,我们只能通过一些间接手段达成模块化的目标,如使用webpack这样的打包工具。
从es6开始,浏览器原生支持模块化。现在你可以使用type="module"
声明js模块脚本。在以下的script标签中,app.js及其依赖都会被浏览器获取:
<script type="module" src="app.js"></script>
基于历史原因,js传统脚本的获取以及执行都会阻塞HTML解析。对于这种情况,你可以使用async
属性促使浏览器异步获取脚本,又或者使用defer
属性延迟到HTML解析完毕后才执行脚本。对于js模块脚本,默认是异步获取的,并且在HTML解析完成后才开始执行。你可以使用async
属性让js模块脚本在完成获取后立即执行,如果这个时候HTML还未完成解析,解析就会被脚本的执行阻塞;defer
属性对js模块脚本无影响。
async
属性、defer
属性与HTML解析过程在运行时上的关系,可以用下面一张图总结:
js模块脚本除了导入js模块,还可以导入css模块以及json模块,但需要使用assert
语句声明其类型:
<script type="module">
import json from 'example.json' assert {type: 'json'}
import css from 'example.css' assert {type: 'css'}
// ...
</script>
importmap
importmap是一个最近(2022年10月5日)才正式写入标准的脚本类型。
任何的模块系统,不管是AMD、commonJs还是es6模块,都有「模块标识符」的概念。模块标识符用于索引一个模块,你可以简单地理解为是模块的名字。很多时候,模块标识符就是代码所在位置的路径,比如下面的代码中,"/node_modules/moment/src/moment.js"
就是这个文件对应的模块的模块标识符:
import moment form "/node_modules/moment/src/moment.js"
在importmap之前,es6模块的模块标识符只支持像上面这样的实际路径,而importMap可以实现对模块标识符的重新映射。比如下面的例子,把"/node_modules/moment/src/moment.js"
映射到"moment"
上;于是,该页面中所有的js模块脚本,都可以统一使用import XXX from "moment"
引入这个模块:
<script type="importmap">
{
"imports": {
"moment": "/node_modules/moment/src/moment.js"
}
}
</script>
<script type="module">
import moment from "moment"
// ...
</script>
每一个页面最多只能有一个importmap。importmap需要使用一个内联的json表示,这个json支持两个顶层的键:
-
imports
:作用于全局的映射,如上面所示。 -
scopes
:作用于局部映射。常用于在页面内使用同一模块的不同版本,比如以下这个例子:<script type="importmap"> { "scopes": { "/a/" : { "moment": "/node_modules/moment/src/moment.js" }, "/b/" : { "moment": "https://cdn.example.com/moment/src/moment.js" } } } </script>
当使用
import "moment"
的时候,不同位置下的脚本会有不同的情况:- 位于
/a/
下的脚本,会引入"/node_modules/moment/src/moment.js"
; - 位于
/b/
下的脚本,会引入"https://cdn.example.com/moment/src/moment.js"
; - 位于
/c/
下的脚本,会报错。
- 位于
importmap还支持多种类型的模块标识符:
-
裸标识符(Bare specifiers),不带有斜杠
/
的标识符,如上面的moment。 -
以斜杠结尾的标识符:可用于映射一类的路径。
<script type="importmap"> { "imports": { "moment/": "/node_modules/moment/src/" } } </script> <script type="module"> import localeData from "moment/locale/zh-cn.js" // ... </script>
-
URL类标识符:包括绝对路径和相对路径。
{ "imports": { "https://cdn.example.com/vue/dist/vue.runtime.esm.js": "/node_modules/vue/dist/vue.runtime.esm.js", "/js/app.mjs": "/js/app-8e0d62a03.mjs", "../helpers/": "https://cdn.example/helpers/" } }
在现实中,three.js很早就在使用importmap了,不过是配合着垫片(shim)使用的。
数据块
当script标签的type
属性不匹配js传统脚本、js模块脚本、importmap任一类型的时候,浏览器会直接忽略这个标签。这种标签在实际开发中经常被用来当作数据块使用。
比如,你可以使用数据块存放一张游戏地图,这个数据块可以用于运行游戏的时候生成地图,也可以用在站内检索,提供特定的能力。
<script src="game-engine.js"></script>
<script type="text/x-game-map">
........U.........e
o............A....e
.....A.....AAA....e
.A..AAA...AAAAA...e
</script>
我们也可以看一些现实中的例子:
-
systemjs:systemjs使得开发者可以在老式浏览器上使用es6模块的语法。它使用
type="systemjs-module"
以及type="systemjs-importmap"
的script标签分别模拟js模块脚本和importmap,这种script标签本质上就是一个数据块,浏览器并不会对这些script标签作任何处理,这些标签会留给systemjs内部进行处理,从而模拟加载模块的过程:<script src="system.js"></script> <script type="systemjs-importmap"> { "imports": { "lodash": "https://unpkg.com/lodash@4.17.10/lodash.js" } } </script> <script type="systemjs-module" src="/js/main.js"></script>
-
three.js:threejs是一个3D库。在它的示例中,常常使用数据块来存放3D渲染模型的数据,如
type="x-shader/x-vertex"
、type="x-shader/x-fragment"
的script标签。文章来源:https://www.toymoban.com/news/detail-449320.html<script id="procedural-vert" type="x-shader/x-vertex"> varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } </script>
值得一提的是,标准建议:在使用数据块的时候,最好使用符合格式的MIME类型,避免标准在未来增加新的类型的时候发生冲突:文章来源地址https://www.toymoban.com/news/detail-449320.html
"text/html" // 符合格式
"text/html;" // 不符合格式
"text/html;charset=uft-8" // 符合格式
到了这里,关于script标签4种的四种用法,你知道几种?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!