script标签4种的四种用法,你知道几种?
本文为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标签。<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类型,避免标准在未来增加新的类型的时候发生冲突:
"text/html" // 符合格式
"text/html;" // 不符合格式
"text/html;charset=uft-8" // 符合格式
script标签4种的四种用法,你知道几种?相关推荐
- 四种Java线程池用法解析
四种Java线程池用法解析 本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 ...
- 庄懂的TA笔记(十三)<特效-混合模式:四种主要透明通道用法 AC,AB,AD,自定义混合>
庄懂的TA笔记(十三)<特效-混合模式:四种主要透明通道用法 AC,AB,AD,自定义混合> 效果展示: 正文: 一.特效类大纲: 1.特效 · 透 2.特效 · 动 3.特效 · 映 二 ...
- java7 javascript引擎_Java7中脚本引擎的一般用法,共三种方法获得JavaScript引擎:名称、文件扩展名、MIME类型 | 学步园...
package com.sino.java7; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; i ...
- 46种厨房常见调料用法大全
食盐.生抽&老抽.醋.料酒&白酒.甜面酱.豆瓣酱.豆豉.番茄酱&番茄沙司.芝麻酱.沙拉酱.白糖.冰糖.红糖.辣椒.麻椒.花椒.八角.香叶.桂皮.黑胡椒&白胡椒.孜然.小 ...
- python print用法可以不加引号吗_第一课print() 函数的用法有以下几种:单刀赴会—不带引号...
print() 函数的用法有以下几种:单刀赴会-不带引号,黄袍加身-搭配单引号.双引号.三引号,让我们一睹风采. 无引号 您已经能够看到下面左侧代码框中,现在空空如也.那么就在这个框里,一字不差地抄上 ...
- ML之4PolyR:利用四次多项式回归4PolyR模型+两种正则化(Lasso/Ridge)在披萨数据集上拟合(train)、价格回归预测(test)
ML之4PolyR:利用四次多项式回归4PolyR模型+两种正则化(Lasso/Ridge)在披萨数据集上拟合(train).价格回归预测(test) 目录 输出结果 设计思路 核心代码 输出结果 设 ...
- MySQL 性能优化:8 种常见 SQL 错误用法!
声明:转载自 MySQL 性能优化:8 种常见 SQL 错误用法! 1.LIMIT 语句 分页查询是最常用的场景之一,但也通常也是最容易出问题的地方.比如对于下面简单的语句,一般 DBA 想到的办法是 ...
- oracle存储多少条数据类型,Oracle目前可以存储极大的对象,这是因为它引入了四种新的数据类型。其中哪一种大对象数据类型在数...
Oracle目前可以存储极大的对象,这是因为它引入了四种新的数据类型.其中哪一种大对象数据类型在数 更多相关问题 谈谈我国幼儿教师的基本权利和义务. 请帮忙给出正确答案和分析,谢谢! 监察机关在办理监 ...
- 分页标签精讲(仿百度雅虎淘宝共23种样式任意切换)-罗春龙-专题视频课程
分页标签精讲(仿百度雅虎淘宝共23种样式任意切换)-217人已学习 课程介绍 仿百度雅虎淘宝实现分页标签,需要学者有一定的JSP基础,该视频深入讲解分页标签业务逻辑处理: 课程收益 ...
最新文章
- Android中三种超实用的滑屏方式汇总(ViewPager、ViewFlipper、ViewFlow)
- 再见,Python。你好,Go 语言
- PMCAFF高端俱乐部首次集结,最顶级产品人的私密俱乐部!
- CodeForces - 786C——二分+模拟?
- 奥运会上刷新亚洲记录的211高校副教授苏炳添论文被扒出,网友:膜拜大神!...
- 【数据库】第四章 JDBC、MyBatis
- php实现一个简单的购物网站
- C - 师--链表的结点插入
- [JavaScript]JavaScript处理iframe的动作
- UVA - 1347
- HDU2044 一只小蜜蜂...
- 使用kitti数据集实现自动驾驶——发布照片、点云、IMU、GPS、显示2D和3D侦测框
- VC++6.0常见问题之fatal error C1083解决方案
- 安装CARLA Simulator错误 安装失败 0x80070005 - 访问被拒绝 Error Setup Failed 0x80070005 - Access is denied
- python画两条曲线_python 实现将多条曲线画在一幅图上的方法
- 进程间通讯SendMessage
- 【夜读】丰富自己的4个习惯,请逼自己养成
- iOS获取设备IP地址
- 短信管理器android,短信夹管理软件-短信夹管理app预约v1.4.3 安卓版-西西软件园...
- API是用来干什么的