本文为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分别是classicmoduleimportmap

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种的四种用法,你知道几种?相关推荐

  1. 四种Java线程池用法解析

    四种Java线程池用法解析 本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 ...

  2. 庄懂的TA笔记(十三)<特效-混合模式:四种主要透明通道用法 AC,AB,AD,自定义混合>

    庄懂的TA笔记(十三)<特效-混合模式:四种主要透明通道用法 AC,AB,AD,自定义混合> 效果展示: 正文: 一.特效类大纲: 1.特效 · 透 2.特效 · 动 3.特效 · 映 二 ...

  3. java7 javascript引擎_Java7中脚本引擎的一般用法,共三种方法获得JavaScript引擎:名称、文件扩展名、MIME类型 | 学步园...

    package com.sino.java7; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; i ...

  4. 46种厨房常见调料用法大全

    食盐.生抽&老抽.醋.料酒&白酒.甜面酱.豆瓣酱.豆豉.番茄酱&番茄沙司.芝麻酱.沙拉酱.白糖.冰糖.红糖.辣椒.麻椒.花椒.八角.香叶.桂皮.黑胡椒&白胡椒.孜然.小 ...

  5. python print用法可以不加引号吗_第一课print() 函数的用法有以下几种:单刀赴会—不带引号...

    print() 函数的用法有以下几种:单刀赴会-不带引号,黄袍加身-搭配单引号.双引号.三引号,让我们一睹风采. 无引号 您已经能够看到下面左侧代码框中,现在空空如也.那么就在这个框里,一字不差地抄上 ...

  6. ML之4PolyR:利用四次多项式回归4PolyR模型+两种正则化(Lasso/Ridge)在披萨数据集上拟合(train)、价格回归预测(test)

    ML之4PolyR:利用四次多项式回归4PolyR模型+两种正则化(Lasso/Ridge)在披萨数据集上拟合(train).价格回归预测(test) 目录 输出结果 设计思路 核心代码 输出结果 设 ...

  7. MySQL 性能优化:8 种常见 SQL 错误用法!

    声明:转载自 MySQL 性能优化:8 种常见 SQL 错误用法! 1.LIMIT 语句 分页查询是最常用的场景之一,但也通常也是最容易出问题的地方.比如对于下面简单的语句,一般 DBA 想到的办法是 ...

  8. oracle存储多少条数据类型,Oracle目前可以存储极大的对象,这是因为它引入了四种新的数据类型。其中哪一种大对象数据类型在数...

    Oracle目前可以存储极大的对象,这是因为它引入了四种新的数据类型.其中哪一种大对象数据类型在数 更多相关问题 谈谈我国幼儿教师的基本权利和义务. 请帮忙给出正确答案和分析,谢谢! 监察机关在办理监 ...

  9. 分页标签精讲(仿百度雅虎淘宝共23种样式任意切换)-罗春龙-专题视频课程

    分页标签精讲(仿百度雅虎淘宝共23种样式任意切换)-217人已学习 课程介绍         仿百度雅虎淘宝实现分页标签,需要学者有一定的JSP基础,该视频深入讲解分页标签业务逻辑处理: 课程收益   ...

最新文章

  1. Android中三种超实用的滑屏方式汇总(ViewPager、ViewFlipper、ViewFlow)
  2. 再见,Python。你好,Go 语言
  3. PMCAFF高端俱乐部首次集结,最顶级产品人的私密俱乐部!
  4. CodeForces - 786C——二分+模拟?
  5. 奥运会上刷新亚洲记录的211高校副教授苏炳添论文被扒出,网友:膜拜大神!...
  6. 【数据库】第四章 JDBC、MyBatis
  7. php实现一个简单的购物网站
  8. C - 师--链表的结点插入
  9. [JavaScript]JavaScript处理iframe的动作
  10. UVA - 1347
  11. HDU2044 一只小蜜蜂...
  12. 使用kitti数据集实现自动驾驶——发布照片、点云、IMU、GPS、显示2D和3D侦测框
  13. VC++6.0常见问题之fatal error C1083解决方案
  14. 安装CARLA Simulator错误 安装失败 0x80070005 - 访问被拒绝 Error Setup Failed 0x80070005 - Access is denied
  15. python画两条曲线_python 实现将多条曲线画在一幅图上的方法
  16. 进程间通讯SendMessage
  17. 【夜读】丰富自己的4个习惯,请逼自己养成
  18. iOS获取设备IP地址
  19. 短信管理器android,短信夹管理软件-短信夹管理app预约v1.4.3 安卓版-西西软件园...
  20. API是用来干什么的

热门文章

  1. 前端基础第一天——HTML
  2. 怎么在30分钟加50个精准微信群?我是这样做的......
  3. Spring配置文件中的import
  4. windows查看CPU核数、线程数
  5. Python 的 object类、type元类
  6. 软件测试的方法和分类与开发模型的变迁
  7. iOS播放多种格式视频
  8. 荣耀x1 鸿蒙系统 优酷投屏,真正的智慧体验 荣耀智慧屏X1智能互联与投屏功能...
  9. 网页走服务器流量吗,刷网站流量等于慢性自杀
  10. 【jdk8的新特性】