關(guān)于ES模塊,本文將提供一些詳細(xì)的解讀,希望對(duì)你有所幫助!
什么是ES模塊?
ECMAScript模塊(簡(jiǎn)稱ES模塊)是2015年推出的 JavaScript 中代碼重用的機(jī)制。在高度碎片化的 JavaScript 模塊場(chǎng)景中,它終于成為了標(biāo)準(zhǔn)。
在2015年之前, JavaScript 還沒(méi)有一個(gè)標(biāo)準(zhǔn)的代碼重用機(jī)制。這方面曾有過(guò)很多標(biāo)準(zhǔn)化的嘗試,導(dǎo)致這些年亂七八糟的碎片化。
你可能聽(tīng)說(shuō)過(guò) AMD 模塊、UMD 或者 CommonJS。沒(méi)有明顯的贏家。終于,隨著 ECMAScript 2015,ES模塊登陸語(yǔ)言。
我們現(xiàn)在有了一個(gè) "官方 "的模塊系統(tǒng)。
ECMAScript模塊無(wú)處不在?
理論上,ECMAScript 模塊應(yīng)該普遍適用于所有 JavaScript 環(huán)境。實(shí)際上,瀏覽器仍然是ES模塊的主要目標(biāo)。
2020年5月,Node.js v12.17.0 發(fā)貨時(shí),支持 ECMAScript 模塊,沒(méi)有標(biāo)志。這意味著我們現(xiàn)在可以在 Node.js 中使用導(dǎo)入和導(dǎo)出,而無(wú)需任何額外的命令行標(biāo)志。
在 ECMAScript 模塊在任何 JavaScript 環(huán)境中普遍工作之前,還有很長(zhǎng)的路要走,但方向是正確的。
ES模塊是怎樣的?
一個(gè)ES模塊就是一個(gè)簡(jiǎn)單的文件,我們可以聲明一個(gè)或多個(gè)出口。以這個(gè)虛構(gòu)的 utils.js 為例。
// utils.js
export function funcA() {
return "Hello named export!";
}
export default function funcB() {
return "Hello default export!";
}
我們這里有兩個(gè)導(dǎo)出。
第一個(gè)是一個(gè)命名的導(dǎo)出,后面是一個(gè)默認(rèn)的導(dǎo)出,表示為導(dǎo)出默認(rèn)。
假設(shè)我們的項(xiàng)目文件夾中住著這個(gè)名為 utils.js 的文件,我們可以在另一個(gè)文件中導(dǎo)入這個(gè)模塊提供的對(duì)象。
如何從ES模塊導(dǎo)入
假設(shè)我們?cè)陧?xiàng)目文件夾中還有一個(gè)名為 consumer.js 的文件。要導(dǎo)入 utils.js 所暴露的函數(shù),我們可以這樣做。
// consumer.js
import { funcA } from "./util.js";
這種語(yǔ)法是一種命名的導(dǎo)入方式,與命名的導(dǎo)出方式有異曲同工之妙。
如果要導(dǎo)入定義為默認(rèn)導(dǎo)出的 funcB,我們可以這樣做:
// consumer.js
import funcB from "./util.js";
如果我們想在一個(gè)文件中同時(shí)導(dǎo)入默認(rèn)導(dǎo)出和命名導(dǎo)出,我們可以將其壓縮為:
// consumer.js
import funcB, { funcA } from "./util.js";
funcB();
funcA();
我們也可以用 star 導(dǎo)入整個(gè)模塊。
import * as myModule from "./util.js";
myModule.funcA();
myModule.default();
要注意,在這種情況下,必須顯式調(diào)用默認(rèn)導(dǎo)出。
要從遠(yuǎn)程模塊導(dǎo)入。
import { createStore } from "https://unpkg.com/redux@4.0.5/es/redux.mjs";
const store = createStore(/* do stuff */)
瀏覽器中的ECMAScript模塊
現(xiàn)代瀏覽器支持 ES 模塊,盡管有一些注意事項(xiàng)。要加載一個(gè)模塊,請(qǐng)?jiān)谀_本標(biāo)簽的 type 屬性中添加模塊。
ECMAScript modules in the browser
<p id="el">The result is: </p>
import { appendResult } from "./myModule.js";
const el = document.getElementById("el");
appendResult(el);
這里 myModule.js 是同一個(gè)項(xiàng)目文件夾下的一個(gè)簡(jiǎn)單模塊。
export function appendResult(element) {
const result = Math.random();
element.innerText += result;
}
雖然可以直接在瀏覽器中使用ES模塊,但現(xiàn)在捆綁 JavaScript 應(yīng)用的任務(wù)仍然是 webpack 等工具的專屬,以獲得最大的靈活性、代碼拆分和對(duì)舊瀏覽器的兼容性。
動(dòng)態(tài)導(dǎo)入
ES 模塊是靜態(tài)的,這意味著我們無(wú)法在運(yùn)行時(shí)更改導(dǎo)入。有了2020年登陸的動(dòng)態(tài)導(dǎo)入,我們可以根據(jù)用戶的交互動(dòng)態(tài)加載我們的代碼(webpack在ECMAScript 2020中提供動(dòng)態(tài)導(dǎo)入功能之前就已經(jīng)提供了)。
考慮一個(gè)簡(jiǎn)單的 HTML,它可以加載一個(gè)腳本。
Dynamic imports
<button id="btn">Load!</button>
也可以考慮用幾個(gè)導(dǎo)出的 JavaScript 模塊。
// util.js
export function funcA() {
console.log("Hello named export!");
}
export default function funcB() {
console.log("Hello default export!");
}
如果要?jiǎng)討B(tài)加載這個(gè)模塊,也許點(diǎn)擊一下,我們可以這樣做。
// loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// loads named export
import("./util.js").then(({ funcA }) => {
funcA();
});
});
在這里,我們通過(guò)重構(gòu)模塊的對(duì)象,只加載命名的導(dǎo)出。
({ funcA }) => {}
ES 模塊實(shí)際上就是 JavaScript 對(duì)象:我們可以重構(gòu)它們的屬性,也可以調(diào)用它們的任何暴露的方法。
要?jiǎng)討B(tài)地導(dǎo)入一個(gè)默認(rèn)的導(dǎo)出,我們可以這樣做。
// loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// loads entire module
// runs default export
import("./util.js").then((module) => {
module.default();
});
});
當(dāng)整體導(dǎo)入一個(gè)模塊時(shí),我們可以使用它的所有輸出。
// loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// loads entire module
// uses everything
import("./util.js").then((module) => {
module.funcA();
module.default();
});
});
還有一種常見(jiàn)的動(dòng)態(tài)導(dǎo)入方式,我們?cè)谖募捻敳刻崛∵壿嫛?/p>
const loadUtil = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
//
});
在這里,loadUtil 將返回一個(gè) Promise,準(zhǔn)備進(jìn)行鏈鎖。
const loadUtil = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
loadUtil().then(module => {
module.funcA();
module.default();
});
});
動(dòng)態(tài)導(dǎo)入看起來(lái)很好,但是它們有什么用呢?
通過(guò)動(dòng)態(tài)導(dǎo)入,我們可以拆分我們的代碼,只在合適的時(shí)刻加載重要的內(nèi)容。在動(dòng)態(tài)導(dǎo)入登陸JavaScript之前,這種模式是webpack這個(gè)模塊捆綁器的專屬。
像React和Vue這樣的前端庫(kù),就大量使用了通過(guò)動(dòng)態(tài)導(dǎo)入進(jìn)行代碼拆分的方式,在響應(yīng)事件時(shí)加載分塊代碼,比如用戶交互或者路由變化。
JSON文件的動(dòng)態(tài)導(dǎo)入
假設(shè)你在代碼庫(kù)的某個(gè)地方有一個(gè)JSON文件person.json。
{
"name": "Jules",
"age": 43
}
現(xiàn)在,你想動(dòng)態(tài)地導(dǎo)入這個(gè)文件,以響應(yīng)一些用戶的交互。
由于JSON文件導(dǎo)出的只是一個(gè)默認(rèn)的導(dǎo)出,它不是一個(gè)函數(shù),所以你只能像這樣訪問(wèn)默認(rèn)的導(dǎo)出。
const loadPerson = () => import("./person.json");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
loadPerson().then(module => {
const { name, age } = module.default;
console.log(name, age);
});
});
這里,我們從默認(rèn)的導(dǎo)出中重構(gòu)name和age。
const { name, age } = module.default;
使用async/await動(dòng)態(tài)導(dǎo)入
import()語(yǔ)句返回的總是一個(gè)Promise,這意味著我們可以對(duì)它使用async/await。
const loadUtil = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", async () => {
const utilsModule = await loadUtil();
utilsModule.funcA();
utilsModule.default();
});
動(dòng)態(tài)導(dǎo)入名稱
當(dāng)用import()導(dǎo)入一個(gè)模塊時(shí),你可以隨心所欲地給它命名,只要保持一致即可。
import("./util.js").then((module) => {
module.funcA();
module.default();
});
或者:
import("./util.js").then((utilModule) => {
utilModule.funcA();
utilModule.default();
});
文章來(lái)源于公眾號(hào):前端開(kāi)發(fā)博客
以上就是W3Cschool編程獅
關(guān)于全面認(rèn)識(shí)ECMAScript模塊的相關(guān)介紹了,希望對(duì)大家有所幫助。