全面認識ECMAScript模塊
關于ES模塊,本文將提供一些詳細的解讀,希望對你有所幫助!
什么是ES模塊?
ECMAScript模塊(簡稱ES模塊)是2015年推出的JavaScript中代碼重用的機制。在高度碎片化的JavaScript模塊場景中,它終于成為了標準。
在2015年之前,JavaScript還沒有一個標準的代碼重用機制。這方面曾有過很多標準化的嘗試,導致這些年亂七八糟的碎片化。
你可能聽說過AMD模塊、UMD或者CommonJS。沒有明顯的贏家。終于,隨著ECMAScript 2015,ES模塊登陸語言。
我們現(xiàn)在有了一個 "官方 "的模塊系統(tǒng)。
ECMAScript模塊無處不在?
理論上,ECMAScript模塊應該普遍適用于所有JavaScript環(huán)境。實際上,瀏覽器仍然是ES模塊的主要目標。
2020年5月,Node.js v12.17.0發(fā)貨時,支持ECMAScript模塊,沒有標志。這意味著我們現(xiàn)在可以在Node.js中使用導入和導出,而無需任何額外的命令行標志。
在ECMAScript模塊在任何JavaScript環(huán)境中普遍工作之前,還有很長的路要走,但方向是正確的。
ES模塊是怎樣的?
一個ES模塊就是一個簡單的文件,我們可以聲明一個或多個出口。以這個虛構的utils.js為例。
// utils.js
export function funcA() {
return "Hello named export!";
}
export default function funcB() {
return "Hello default export!";
}我們這里有兩個導出。
第一個是一個命名的導出,后面是一個默認的導出,表示為導出默認。
假設我們的項目文件夾中住著這個名為utils.js的文件,我們可以在另一個文件中導入這個模塊提供的對象。
如何從ES模塊導入
假設我們在項目文件夾中還有一個名為consumer.js的文件。要導入utils.js所暴露的函數(shù),我們可以這樣做。
// consumer.js
import { funcA } from "./util.js";這種語法是一種命名的導入方式,與命名的導出方式有異曲同工之妙。
如果要導入定義為默認導出的funcB,我們可以這樣做:
// consumer.js
import funcB from "./util.js";如果我們想在一個文件中同時導入默認導出和命名導出,我們可以將其壓縮為:
// consumer.js
import funcB, { funcA } from "./util.js";
funcB();
funcA();我們也可以用star導入整個模塊。
import * as myModule from "./util.js";
myModule.funcA();
myModule.default();要注意,在這種情況下,必須顯式調(diào)用默認導出。
要從遠程模塊導入。
import { createStore } from "https://unpkg.com/[email protected]/es/redux.mjs";
const store = createStore(/* do stuff */)瀏覽器中的ECMAScript模塊
現(xiàn)代瀏覽器支持ES模塊,盡管有一些注意事項。要加載一個模塊,請在腳本標簽的type屬性中添加模塊。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ECMAScript modules in the browsertitle>
head>
<body>
<p id="el">The result is: p>
body>
<script type="module">
import { appendResult } from "./myModule.js";
const el = document.getElementById("el");
appendResult(el);
script>
html>這里myModule.js是同一個項目文件夾下的一個簡單模塊。
export function appendResult(element) {
const result = Math.random();
element.innerText += result;
}雖然可以直接在瀏覽器中使用ES模塊,但現(xiàn)在捆綁JavaScript應用的任務仍然是webpack等工具的專屬,以獲得最大的靈活性、代碼拆分和對舊瀏覽器的兼容性。
動態(tài)導入
ES模塊是靜態(tài)的,這意味著我們無法在運行時更改導入。有了2020年登陸的動態(tài)導入,我們可以根據(jù)用戶的交互動態(tài)加載我們的代碼(webpack在ECMAScript 2020中提供動態(tài)導入功能之前就已經(jīng)提供了)。
考慮一個簡單的HTML,它可以加載一個腳本。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dynamic importstitle>
head>
<body>
<button id="btn">Load!button>
body>
<script src="loader.js">script>
html>也可以考慮用幾個導出的JavaScript模塊。
// util.js
export function funcA() {
console.log("Hello named export!");
}
export default function funcB() {
console.log("Hello default export!");
}如果要動態(tài)加載這個模塊,也許點擊一下,我們可以這樣做。
// loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// loads named export
import("./util.js").then(({ funcA }) => {
funcA();
});
});在這里,我們通過重構模塊的對象,只加載命名的導出。
({ funcA }) => {}ES模塊實際上就是JavaScript對象:我們可以重構它們的屬性,也可以調(diào)用它們的任何暴露的方法。
要動態(tài)地導入一個默認的導出,我們可以這樣做。
// loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// loads entire module
// runs default export
import("./util.js").then((module) => {
module.default();
});
});當整體導入一個模塊時,我們可以使用它的所有輸出。
// loader.js
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// loads entire module
// uses everything
import("./util.js").then((module) => {
module.funcA();
module.default();
});
});還有一種常見的動態(tài)導入方式,我們在文件的頂部提取邏輯。
const loadUtil = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
//
});在這里,loadUtil將返回一個Promise,準備進行鏈鎖。
const loadUtil = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
loadUtil().then(module => {
module.funcA();
module.default();
});
});動態(tài)導入看起來很好,但是它們有什么用呢?
通過動態(tài)導入,我們可以拆分我們的代碼,只在合適的時刻加載重要的內(nèi)容。在動態(tài)導入登陸JavaScript之前,這種模式是webpack這個模塊捆綁器的專屬。
像React和Vue這樣的前端庫,就大量使用了通過動態(tài)導入進行代碼拆分的方式,在響應事件時加載分塊代碼,比如用戶交互或者路由變化。
JSON文件的動態(tài)導入
假設你在代碼庫的某個地方有一個JSON文件person.json。
{
"name": "Jules",
"age": 43
}現(xiàn)在,你想動態(tài)地導入這個文件,以響應一些用戶的交互。
由于JSON文件導出的只是一個默認的導出,它不是一個函數(shù),所以你只能像這樣訪問默認的導出。
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);
});
});這里,我們從默認的導出中重構name和age。
const { name, age } = module.default;使用async/await動態(tài)導入
import()語句返回的總是一個Promise,這意味著我們可以對它使用async/await。
const loadUtil = () => import("./util.js");
const btn = document.getElementById("btn");
btn.addEventListener("click", async () => {
const utilsModule = await loadUtil();
utilsModule.funcA();
utilsModule.default();
});動態(tài)導入名稱
當用import()導入一個模塊時,你可以隨心所欲地給它命名,只要保持一致即可。
import("./util.js").then((module) => {
module.funcA();
module.default();
});或者:
import("./util.js").then((utilModule) => {
utilModule.funcA();
utilModule.default();
});關注數(shù):10億+?文章數(shù):10億+
粉絲量:10億+?點擊量:10億+
?懸賞博主專區(qū)請掃描這里
喜愛數(shù):?1億+?發(fā)帖數(shù):?1億+
回帖數(shù):?1億+?結貼率:?99.9%
—————END—————
喜歡本文的朋友,歡迎關注公眾號?程序員哆啦A夢,收看更多精彩內(nèi)容
點個[在看],是對小達最大的支持!
如果覺得這篇文章還不錯,來個【分享、點贊、在看】三連吧,讓更多的人也看到~



