本文為HTML標準解讀系列文章,其他文章詳見這里。
在一個HTML頁面中執(zhí)行js腳本有很多方式,包括但不限于以下幾種:
- 使用script標簽執(zhí)行腳本;
- 使用
javascript:URL
的導(dǎo)航; - 使用DOM上的事件監(jiān)聽機制;
- 使用svg相關(guān)技術(shù)中的腳本能力;
在這些方式中,使用最多的無疑是第一種。script標簽允許開發(fā)者給頁面插入js腳本,而根據(jù)type
屬性的值,可以把script元素分成4種不同的類型:
類型 | 對應(yīng)的type屬性值 | 描述 |
---|---|---|
js傳統(tǒng)腳本(classic script) | 沒有聲明type屬性,或type屬性值為空,或type屬性值匹配任一JavaScript MIME類型(如text/javascript )。 |
以ECMAScript頂層Script語法規(guī)則進行解析的腳本。 |
js模塊腳本(module script) | “module” | 以ECMAScript頂層Module語法規(guī)則進行解析的腳本。 |
Imports map | “importmap” | 控制頁面內(nèi)模塊標識符(module specifier)的解析。 |
數(shù)據(jù)塊 | 除了上述以外的其他值 | 瀏覽器不會被對其作處理,內(nèi)部的文本可以作為數(shù)據(jù)在其他腳本中時候。 |
對于前面三種類型,可以使用HTMLScriptElement.supports(type)
的方法來檢測瀏覽器是否支持這些類型,對應(yīng)的參數(shù)type
分別是classic
、module
或importmap
。
js傳統(tǒng)腳本
js傳統(tǒng)腳本是我們使用最多的script類型。我們可以在標簽內(nèi)直接寫js代碼,也可以通過src屬性引入一個外部的js文件。
基于歷史原因,script標簽內(nèi)容的解析有一些奇怪的規(guī)則。比如,以下的script標簽都無法按照預(yù)期運行:
<!-- 1: script標簽把字符串內(nèi)容</script>看成是閉合標簽 -->
<script>
const example = "script的閉合標簽是</script>";
console.log(example);
</script>
<!-- 2: script標簽把<!--看成是注釋的起始標簽 -->
<script>
if (x <!--y) { ... }
</script>
為了避免這些坑,標準建議把所有script標簽里的字符串、正則表達式、注釋內(nèi)容里面的<!--
、<script
、</script
都使用\x3C!--
、\x3Cscript
、\x3C/script
轉(zhuǎn)義,并且避免在js表達式中使用這類寫法。所以,以上的問題可以這么修正:
<script>
const example = "script的閉合標簽是\x3C/script>";
console.log(example);
</script>
<script>
if (x < !--y) { ... }
</script>
js模塊腳本
把代碼拆解成不同的模塊是程序員應(yīng)對復(fù)雜度的一個重要手段。在js模塊腳本獲得瀏覽器原生支持之前,我們只能通過一些間接手段達成模塊化的目標,如使用webpack這樣的打包工具。
從es6開始,瀏覽器原生支持模塊化?,F(xiàn)在你可以使用type="module"
聲明js模塊腳本。在以下的script標簽中,app.js及其依賴都會被瀏覽器獲取:
<script type="module" src="app.js"></script>
基于歷史原因,js傳統(tǒng)腳本的獲取以及執(zhí)行都會阻塞HTML解析。對于這種情況,你可以使用async
屬性促使瀏覽器異步獲取腳本,又或者使用defer
屬性延遲到HTML解析完畢后才執(zhí)行腳本。對于js模塊腳本,默認是異步獲取的,并且在HTML解析完成后才開始執(zhí)行。你可以使用async
屬性讓js模塊腳本在完成獲取后立即執(zhí)行,如果這個時候HTML還未完成解析,解析就會被腳本的執(zhí)行阻塞;defer
屬性對js模塊腳本無影響。
async
屬性、defer
屬性與HTML解析過程在運行時上的關(guān)系,可以用下面一張圖總結(jié):
js模塊腳本除了導(dǎo)入js模塊,還可以導(dǎo)入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日)才正式寫入標準的腳本類型。
任何的模塊系統(tǒng),不管是AMD、commonJs還是es6模塊,都有「模塊標識符」的概念。模塊標識符用于索引一個模塊,你可以簡單地理解為是模塊的名字。很多時候,模塊標識符就是代碼所在位置的路徑,比如下面的代碼中,"/node_modules/moment/src/moment.js"
就是這個文件對應(yīng)的模塊的模塊標識符:
import moment form "/node_modules/moment/src/moment.js"
在importmap之前,es6模塊的模塊標識符只支持像上面這樣的實際路徑,而importMap可以實現(xiàn)對模塊標識符的重新映射。比如下面的例子,把"/node_modules/moment/src/moment.js"
映射到"moment"
上;于是,該頁面中所有的js模塊腳本,都可以統(tǒng)一使用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需要使用一個內(nèi)聯(lián)的json表示,這個json支持兩個頂層的鍵:
-
imports
:作用于全局的映射,如上面所示。 -
scopes
:作用于局部映射。常用于在頁面內(nèi)使用同一模塊的不同版本,比如以下這個例子:<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。 -
以斜杠結(jié)尾的標識符:可用于映射一類的路徑。
<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/" } }
在現(xiàn)實中,three.js很早就在使用importmap了,不過是配合著墊片(shim)使用的。
數(shù)據(jù)塊
當script標簽的type
屬性不匹配js傳統(tǒng)腳本、js模塊腳本、importmap任一類型的時候,瀏覽器會直接忽略這個標簽。這種標簽在實際開發(fā)中經(jīng)常被用來當作數(shù)據(jù)塊使用。
比如,你可以使用數(shù)據(jù)塊存放一張游戲地圖,這個數(shù)據(jù)塊可以用于運行游戲的時候生成地圖,也可以用在站內(nèi)檢索,提供特定的能力。
<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>
我們也可以看一些現(xiàn)實中的例子:
-
systemjs:systemjs使得開發(fā)者可以在老式瀏覽器上使用es6模塊的語法。它使用
type="systemjs-module"
以及type="systemjs-importmap"
的script標簽分別模擬js模塊腳本和importmap,這種script標簽本質(zhì)上就是一個數(shù)據(jù)塊,瀏覽器并不會對這些script標簽作任何處理,這些標簽會留給systemjs內(nèi)部進行處理,從而模擬加載模塊的過程:<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庫。在它的示例中,常常使用數(shù)據(jù)塊來存放3D渲染模型的數(shù)據(jù),如
type="x-shader/x-vertex"
、type="x-shader/x-fragment"
的script標簽。文章來源:http://www.zghlxwxcb.cn/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>
值得一提的是,標準建議:在使用數(shù)據(jù)塊的時候,最好使用符合格式的MIME類型,避免標準在未來增加新的類型的時候發(fā)生沖突:文章來源地址http://www.zghlxwxcb.cn/news/detail-449320.html
"text/html" // 符合格式
"text/html;" // 不符合格式
"text/html;charset=uft-8" // 符合格式
到了這里,關(guān)于script標簽4種的四種用法,你知道幾種?的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!