溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

JavaScript引入模塊的歷史簡(jiǎn)介是怎樣的

發(fā)布時(shí)間:2021-09-30 11:23:12 來(lái)源:億速云 閱讀:105 作者:柒染 欄目:web開(kāi)發(fā)

JavaScript引入模塊的歷史簡(jiǎn)介是怎樣的,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

隨著我們的應(yīng)用越來(lái)越大,我們想要將其拆分成多個(gè)文件,即所謂的“模塊(module)”。一個(gè)模塊可以包含用于特定目的的類(lèi)或函數(shù)庫(kù)。

很長(zhǎng)一段時(shí)間,JavaScript  都沒(méi)有語(yǔ)言級(jí)(language-level)的模塊語(yǔ)法。這不是一個(gè)問(wèn)題,因?yàn)樽畛醯哪_本又小又簡(jiǎn)單,所以沒(méi)必要將其模塊化。

但是最終腳本變得越來(lái)越復(fù)雜,因此社區(qū)發(fā)明了許多種方法來(lái)將代碼組織到模塊中,使用特殊的庫(kù)按需加載模塊。

列舉一些(出于歷史原因):

  • AMD —— 最古老的模塊系統(tǒng)之一,最初由 require.js 庫(kù)實(shí)現(xiàn)。

  • CommonJS —— 為 Node.js 服務(wù)器創(chuàng)建的模塊系統(tǒng)。

  • UMD —— 另外一個(gè)模塊系統(tǒng),建議作為通用的模塊系統(tǒng),它與 AMD 和 CommonJS 都兼容。

現(xiàn)在,它們都在慢慢成為歷史的一部分,但我們?nèi)匀豢梢栽谂f腳本中找到它們。

語(yǔ)言級(jí)的模塊系統(tǒng)在 2015 年的時(shí)候出現(xiàn)在了標(biāo)準(zhǔn)(ES6)中,此后逐漸發(fā)展,現(xiàn)在已經(jīng)得到了所有主流瀏覽器和 Node.js  的支持。因此,我們將從現(xiàn)在開(kāi)始學(xué)習(xí)現(xiàn)代 JavaScript 模塊(module)。

一、什么是模塊?

一個(gè)模塊(module)就是一個(gè)文件。一個(gè)腳本就是一個(gè)模塊。就這么簡(jiǎn)單。

模塊可以相互加載,并可以使用特殊的指令 export 和 import 來(lái)交換功能,從另一個(gè)模塊調(diào)用一個(gè)模塊的函數(shù):

export 關(guān)鍵字標(biāo)記了可以從當(dāng)前模塊外部訪問(wèn)的變量和函數(shù)。

import 關(guān)鍵字允許從其他模塊導(dǎo)入功能。

例如,我們有一個(gè) sayHi.js 文件導(dǎo)出了一個(gè)函數(shù):

//  sayHi.js export function sayHi(user) {   alert(`Hello, ${user}!`); }

……然后另一個(gè)文件可能導(dǎo)入并使用了這個(gè)函數(shù):

//  main.js import {sayHi} from './sayHi.js';  alert(sayHi); // function... sayHi('John'); // Hello, John!

import 指令通過(guò)相對(duì)于當(dāng)前文件的路徑 ./sayHi.js 加載模塊,并將導(dǎo)入的函數(shù) sayHi 分配(assign)給相應(yīng)的變量。

讓我們?cè)跒g覽器中運(yùn)行一下這個(gè)示例。

由于模塊支持特殊的關(guān)鍵字和功能,因此我們必須通過(guò)使用<script type="module"> 特性(attribute)來(lái)告訴瀏覽器,此腳本應(yīng)該被當(dāng)作模塊(module)來(lái)對(duì)待。

像這樣:

<!doctype html> <script type="module">   import {sayHi} from './say.js';    document.body.innerHTML = sayHi('John'); </script>

瀏覽器會(huì)自動(dòng)獲取并解析(evaluate)導(dǎo)入的模塊(如果需要,還可以分析該模塊的導(dǎo)入),然后運(yùn)行該腳本。

模塊只通過(guò) HTTP(s) 工作,在本地文件則不行

如果你嘗試通過(guò) file:// 協(xié)議在本地打開(kāi)一個(gè)網(wǎng)頁(yè),你會(huì)發(fā)現(xiàn) import/export 指令不起作用。你可以使用本地 Web 服務(wù)器,例如  static-server,或者使用編輯器的“實(shí)時(shí)服務(wù)器”功能,例如 VS Code 的 Live Server Extension 來(lái)測(cè)試模塊。

二、模塊核心功能

與“常規(guī)”腳本相比,模塊有什么不同呢?

下面是一些核心的功能,對(duì)瀏覽器和服務(wù)端的 JavaScript 來(lái)說(shuō)都有效。

三、始終使用 “use strict”

模塊始終默認(rèn)使用 use strict,例如,對(duì)一個(gè)未聲明的變量賦值將產(chǎn)生錯(cuò)誤(譯注:在瀏覽器控制臺(tái)可以看到 error 信息)。

<script type="module">   a = 5; // error </script>

四、模塊級(jí)作用域

每個(gè)模塊都有自己的頂級(jí)作用域(top-level scope)。換句話(huà)說(shuō),一個(gè)模塊中的頂級(jí)作用域變量和函數(shù)在其他腳本中是不可見(jiàn)的。

在下面這個(gè)例子中,我們導(dǎo)入了兩個(gè)腳本,hello.js 嘗試使用在 user.js 中聲明的變量 user,失敗了:

<!doctype html> <script type="module" src="user.js"></script> <script type="module" src="hello.js"></script>

模塊期望 export 它們想要被外部訪問(wèn)的內(nèi)容,并 import 它們所需要的內(nèi)容。

所以,我們應(yīng)該將 user.js 導(dǎo)入到 hello.js 中,并從中獲取所需的功能,而不要依賴(lài)于全局變量。

這是正確的變體:

import {user} from './user.js';  document.body.innerHTML = user; // John

在瀏覽器中,每個(gè) <script type="module"> 也存在獨(dú)立的頂級(jí)作用域(譯注:在瀏覽器控制臺(tái)可以看到 error 信息)。

<script type="module">   // 變量?jī)H在這個(gè) module script 內(nèi)可見(jiàn)   let user = "John"; </script>  <script type="module">   alert(user); // Error: user is not defined </script>

如果我們真的需要?jiǎng)?chuàng)建一個(gè) window-level 的全局變量,我們可以將其明確地賦值給 window,并以 window.user  來(lái)訪問(wèn)它。但是這需要你有足夠充分的理由,否則就不要這樣做。

五、模塊代碼僅在第一次導(dǎo)入時(shí)被解析

如果同一個(gè)模塊被導(dǎo)入到多個(gè)其他位置,那么它的代碼僅會(huì)在第一次導(dǎo)入時(shí)執(zhí)行,然后將導(dǎo)出(export)的內(nèi)容提供給所有的導(dǎo)入(importer)。

這有很重要的影響。讓我們通過(guò)示例來(lái)看一下:

首先,如果執(zhí)行一個(gè)模塊中的代碼會(huì)帶來(lái)副作用(side-effect),例如顯示一條消息,那么多次導(dǎo)入它只會(huì)觸發(fā)一次顯示 &mdash;&mdash; 即第一次:

  1. //  alert.js 

  2. alert("Module is evaluated!"); 


// 在不同的文件中導(dǎo)入相同的模塊  //  1.js import `./alert.js`; // Module is evaluated!  //  2.js import `./alert.js`; // (什么都不顯示)

在實(shí)際開(kāi)發(fā)中,頂級(jí)模塊代碼主要用于初始化,內(nèi)部數(shù)據(jù)結(jié)構(gòu)的創(chuàng)建,并且如果我們希望某些東西可以重用 &mdash; 請(qǐng)導(dǎo)出它。

下面是一個(gè)高級(jí)點(diǎn)的例子。

我們假設(shè)一個(gè)模塊導(dǎo)出了一個(gè)對(duì)象:

//  admin.js export let admin = {   name: "John" };

如果這個(gè)模塊被導(dǎo)入到多個(gè)文件中,模塊僅在第一次被導(dǎo)入時(shí)被解析,并創(chuàng)建 admin 對(duì)象,然后將其傳入到所有的導(dǎo)入。

所有的導(dǎo)入都只獲得了一個(gè)唯一的 admin 對(duì)象:

//  1.js import {admin} from './admin.js'; admin.name = "Pete";  //  2.js import {admin} from './admin.js'; alert(admin.name); // Pete  // 1.js 和 2.js 導(dǎo)入的是同一個(gè)對(duì)象 // 在 1.js 中對(duì)對(duì)象做的更改,在 2.js 中也是可見(jiàn)的

所以,讓我們重申一下 &mdash;&mdash; 模塊只被執(zhí)行一次。生成導(dǎo)出,然后它被分享給所有對(duì)其的導(dǎo)入,所以如果某個(gè)地方修改了 admin  對(duì)象,其他的模塊也能看到這個(gè)修改。

這種行為讓我們可以在首次導(dǎo)入時(shí) 設(shè)置 模塊。我們只需要設(shè)置其屬性一次,然后在進(jìn)一步的導(dǎo)入中就都可以直接使用了。

例如,下面的 admin.js 模塊可能提供了特定的功能,但是希望憑證(credential)從外部進(jìn)入 admin 對(duì)象:

//  admin.js export let admin = { };  export function sayHi() {   alert(`Ready to serve, ${admin.name}!`); }

在 init.js 中 &mdash;&mdash; 我們 APP 的第一個(gè)腳本,設(shè)置了 admin.name。現(xiàn)在每個(gè)位置都能看到它,包括在 admin.js  內(nèi)部的調(diào)用。

//  init.js import {admin} from './admin.js'; admin.name = "Pete";

另一個(gè)模塊也可以看到 admin.name:

//  other.js import {admin, sayHi} from './admin.js';  alert(admin.name); // Pete  sayHi(); // Ready to serve, Pete!

六、import.meta

import.meta 對(duì)象包含關(guān)于當(dāng)前模塊的信息。

它的內(nèi)容取決于其所在的環(huán)境。在瀏覽器環(huán)境中,它包含當(dāng)前腳本的 URL,或者如果它是在 HTML 中的話(huà),則包含當(dāng)前頁(yè)面的 URL。

<script type="module">   alert(import.meta.url); // 腳本的 URL(對(duì)于內(nèi)嵌腳本來(lái)說(shuō),則是當(dāng)前 HTML 頁(yè)面的 URL) </script>

七、在一個(gè)模塊中,“this” 是 undefined

這是一個(gè)小功能,但為了完整性,我們應(yīng)該提到它。

在一個(gè)模塊中,頂級(jí) this 是 undefined。

將其與非模塊腳本進(jìn)行比較會(huì)發(fā)現(xiàn),非模塊腳本的頂級(jí) this 是全局對(duì)象:

<script>   alert(this); // window </script>  <script type="module">   alert(this); // undefined </script>

八、瀏覽器特定功能

與常規(guī)腳本相比,擁有 type="module" 標(biāo)識(shí)的腳本有一些特定于瀏覽器的差異。

如果你是第一次閱讀或者你不打算在瀏覽器中使用 JavaScript,那么你可以跳過(guò)本節(jié)內(nèi)容。

九、模塊腳本是延遲的

模塊腳本 總是 被延遲的,與 defer 特性(在 腳本:async,defer 一章中描述的)對(duì)外部腳本和內(nèi)聯(lián)腳本(inline  script)的影響相同。

也就是說(shuō):

  • 下載外部模塊腳本 <script type="module" src="..."> 不會(huì)阻塞 HTML 的處理,它們會(huì)與其他資源并行加載。

  • 模塊腳本會(huì)等到 HTML 文檔完全準(zhǔn)備就緒(即使它們很小并且比 HTML 加載速度更快),然后才會(huì)運(yùn)行。

  • 保持腳本的相對(duì)順序:在文檔中排在前面的腳本先執(zhí)行。

它的一個(gè)副作用是,模塊腳本總是會(huì)“看到”已完全加載的 HTML 頁(yè)面,包括在它們下方的 HTML 元素。

例如:

<script type="module">   alert(typeof button); // object:腳本可以“看見(jiàn)”下面的 button   // 因?yàn)槟K是被延遲的(deferred,所以模塊腳本會(huì)在整個(gè)頁(yè)面加載完成后才運(yùn)行 </script>  相較于下面這個(gè)常規(guī)腳本:  <script>   alert(typeof button); // button 為 undefined,腳本看不到下面的元素   // 常規(guī)腳本會(huì)立即運(yùn)行,常規(guī)腳本的運(yùn)行是在在處理頁(yè)面的其余部分之前進(jìn)行的 </script>  <button id="button">Button</button>

請(qǐng)注意:上面的第二個(gè)腳本實(shí)際上要先于前一個(gè)腳本運(yùn)行!所以我們會(huì)先看到 undefined,然后才是 object。

這是因?yàn)槟K腳本是被延遲的,所以要等到 HTML 文檔被處理完成才會(huì)執(zhí)行它。而常規(guī)腳本則會(huì)立即運(yùn)行,所以我們會(huì)先看到常規(guī)腳本的輸出。

當(dāng)使用模塊腳本時(shí),我們應(yīng)該知道 HTML 頁(yè)面在加載時(shí)就會(huì)顯示出來(lái),在 HTML 頁(yè)面加載完成后才會(huì)執(zhí)行 JavaScript 模塊,因此用戶(hù)可能會(huì)在  JavaScript 應(yīng)用程序準(zhǔn)備好之前看到該頁(yè)面。某些功能那時(shí)可能還無(wú)法正使用。我們應(yīng)該放置“加載指示器(loading  indicator)”,否則,請(qǐng)確保不會(huì)使用戶(hù)感到困惑。

十、Async 適用于內(nèi)聯(lián)腳本(inline script)

對(duì)于非模塊腳本,async 特性(attribute)僅適用于外部腳本。異步腳本會(huì)在準(zhǔn)備好后立即運(yùn)行,獨(dú)立于其他腳本或 HTML 文檔。

對(duì)于模塊腳本,它也適用于內(nèi)聯(lián)腳本。

例如,下面的內(nèi)聯(lián)腳本具有 async 特性,因此它不會(huì)等待任何東西。

它執(zhí)行導(dǎo)入(fetch ./analytics.js),并在準(zhǔn)備導(dǎo)入完成時(shí)運(yùn)行,即使 HTML 文檔還未完成,或者其他腳本仍在等待處理中。

這對(duì)于不依賴(lài)任何其他東西的功能來(lái)說(shuō)是非常棒的,例如計(jì)數(shù)器,廣告,文檔級(jí)事件監(jiān)聽(tīng)器。

<!-- 所有依賴(lài)都獲取完成(analytics.js)然后腳本開(kāi)始運(yùn)行 --> <!-- 不會(huì)等待 HTML 文檔或者其他 <script> 標(biāo)簽 --> <script async type="module">   import {counter} from './analytics.js';    counter.count(); </script>

十一、外部腳本

具有 type="module" 的外部腳本(external script)在兩個(gè)方面有所不同:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 具有相同 src 的外部腳本僅運(yùn)行一次:<!-- 腳本 my.js 被加載完成(fetched)并只被運(yùn)行一次 --> <script type="module" src="my.js"></script> <script type="module" src="my.js"></script>

  3. 從另一個(gè)源(例如另一個(gè)網(wǎng)站)獲取的外部腳本需要 CORS header,如我們?cè)?Fetch:跨源請(qǐng)求 一章中所講的那樣。換句話(huà)說(shuō),如果一個(gè)模塊腳本是從另一個(gè)源獲取的,則遠(yuǎn)程服務(wù)器必須提供表示允許獲取的 header Access-Control-Allow-Origin。<!-- another-site.com 必須提供 Access-Control-Allow-Origin --> <!-- 否則,腳本將無(wú)法執(zhí)行 --> <script type="module" src="http://another-site.com/their.js"></script>默認(rèn)這樣做可以確保更好的安全性。

十二、不允許裸模塊(“bare” module)

在瀏覽器中,import 必須給出相對(duì)或絕對(duì)的 URL 路徑。沒(méi)有任何路徑的模塊被稱(chēng)為“裸(bare)”模塊。在 import 中不允許這種模塊。

例如,下面這個(gè) import 是無(wú)效的:

import {sayHi} from 'sayHi'; // Error,“裸”模塊 // 模塊必須有一個(gè)路徑,例如 './sayHi.js' 或者其他任何路徑

某些環(huán)境,像 Node.js 或者打包工具(bundle  tool)允許沒(méi)有任何路徑的裸模塊,因?yàn)樗鼈冇凶约旱牟檎夷K的方法和鉤子(hook)來(lái)對(duì)它們進(jìn)行微調(diào)。但是瀏覽器尚不支持裸模塊。

十三、兼容性,“nomodule”

舊時(shí)的瀏覽器不理解 type="module"。未知類(lèi)型的腳本會(huì)被忽略。對(duì)此,我們可以使用 nomodule 特性來(lái)提供一個(gè)后備:

<script type="module">   alert("Runs in modern browsers"); </script>  <script nomodule>   alert("Modern browsers know both type=module and nomodule, so skip this")   alert("Old browsers ignore script with unknown type=module, but execute this."); </script>

十四、構(gòu)建工具

在實(shí)際開(kāi)發(fā)中,瀏覽器模塊很少被以“原始”形式進(jìn)行使用。通常,我們會(huì)使用一些特殊工具,例如  Webpack,將它們打包在一起,然后部署到生產(chǎn)環(huán)境的服務(wù)器。

使用打包工具的一個(gè)好處是 &mdash;&mdash; 它們可以更好地控制模塊的解析方式,允許我們使用裸模塊和更多的功能,例如 CSS/HTML 模塊等。

構(gòu)建工具做以下這些事兒:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 從一個(gè)打算放在 HTML 中的 <script type="module"> “主”模塊開(kāi)始。

  3. 分析它的依賴(lài):它的導(dǎo)入,以及它的導(dǎo)入的導(dǎo)入等。

  4. 使用所有模塊構(gòu)建一個(gè)文件(或者多個(gè)文件,這是可調(diào)的),并用打包函數(shù)(bundler function)替代原生的 import 調(diào)用,以使其正常工作。還支持像 HTML/CSS 模塊等“特殊”的模塊類(lèi)型。

  5. 在處理過(guò)程中,可能會(huì)應(yīng)用其他轉(zhuǎn)換和優(yōu)化:刪除無(wú)法訪問(wèn)的代碼。刪除未使用的導(dǎo)出(“tree-shaking”)。刪除特定于開(kāi)發(fā)的像 console 和 debugger 這樣的語(yǔ)句。可以使用 Babel 將前沿的現(xiàn)代的 JavaScript 語(yǔ)法轉(zhuǎn)換為具有類(lèi)似功能的舊的 JavaScript 語(yǔ)法。壓縮生成的文件(刪除空格,用短的名字替換變量等)。

如果我們使用打包工具,那么腳本會(huì)被打包進(jìn)一個(gè)單一文件(或者幾個(gè)文件),在這些腳本中的 import/export 語(yǔ)句會(huì)被替換成特殊的打包函數(shù)(bundler function)。因此,最終打包好的腳本中不包含任何 import/export,它也不需要 type="module",我們可以將其放入常規(guī)的 <script>:

<!-- 假設(shè)我們從諸如 Webpack 這類(lèi)的打包工具中獲得了 "bundle.js" 腳本 --> <script src="bundle.js"></script>

也就是說(shuō),原生模塊也是可以使用的。所以,我們?cè)谶@兒將不會(huì)使用 Webpack:你可以稍后再配置它。

十五、總結(jié)

下面總結(jié)一下模塊的核心概念:

  1. 一個(gè)模塊就是一個(gè)文件。瀏覽器需要使用 <script type="module"> 以使 import/export 可以工作。模塊(譯注:相較于常規(guī)腳本)有幾點(diǎn)差別:默認(rèn)是延遲解析的(deferred)。Async 可用于內(nèi)聯(lián)腳本。要從另一個(gè)源(域/協(xié)議/端口)加載外部腳本,需要 CORS header。重復(fù)的外部腳本會(huì)被忽略

  2. 模塊具有自己的本地頂級(jí)作用域,并可以通過(guò) import/export 交換功能。

  3. 模塊始終使用 use strict。

  4. 模塊代碼只執(zhí)行一次。導(dǎo)出僅創(chuàng)建一次,然后會(huì)在導(dǎo)入之間共享。

當(dāng)我們使用模塊時(shí),每個(gè)模塊都會(huì)實(shí)現(xiàn)特定功能并將其導(dǎo)出。然后我們使用 import 將其直接導(dǎo)入到需要的地方即可。瀏覽器會(huì)自動(dòng)加載并解析腳本。

在生產(chǎn)環(huán)境中,出于性能和其他原因,開(kāi)發(fā)者經(jīng)常使用諸如 Webpack 之類(lèi)的打包工具將模塊打包到一起。

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI