import()
Baseline 廣泛可用 *
import() 語法,通常稱為*動態匯入*,是一個類似函式的表示式,允許非同步和動態地將一個 ECMAScript 模組載入到一個可能非模組的環境中。
與宣告式匯入不同,動態匯入只在需要時才被評估,並允許更大的語法靈活性。
語法
import(moduleName)
import(moduleName, options)
import() 呼叫是一種非常類似於函式呼叫的語法,但 import 本身是一個關鍵字,而不是一個函式。你不能像 const myImport = import 那樣給它起別名,這樣做會丟擲一個 SyntaxError。
引數
moduleName-
要匯入的模組。模組說明符的評估由宿主環境指定,但總是遵循與靜態 import 宣告相同的演算法。
options-
一個包含匯入選項的物件。識別以下鍵
返回值
返回一個 promise,它將
- 如果引用的模組成功載入和評估,則兌現(fulfills)為一個模組名稱空間物件:一個包含
moduleName中所有匯出的物件。 - 如果
moduleName的字串強制轉換丟擲錯誤,則拒絕(rejects)並返回丟擲的錯誤。 - 如果模組的獲取和載入因任何原因失敗,則拒絕並返回一個由實現定義的錯誤(Node 使用一個通用的
Error,而所有瀏覽器都使用TypeError)。常見原因可能包括- 在基於檔案系統的模組系統中(例如 Node.js),如果訪問檔案系統失敗(許可權被拒絕、檔案未找到等)。
- 在基於 Web 的模組系統中(例如瀏覽器),如果網路請求失敗(未連線到網際網路、CORS 問題等)或發生 HTTP 錯誤(404、500 等)。
- 如果引用模組的評估丟擲錯誤,則拒絕並返回丟擲的錯誤。
注意: import() 絕不會同步丟擲錯誤。
描述
import 宣告語法(import something from "somewhere")是靜態的,並且總是會導致被匯入的模組在載入時被評估。動態匯入允許人們規避 import 宣告的語法僵化,並根據條件或按需載入模組。以下是你可能需要使用動態匯入的一些原因
- 當靜態匯入顯著減慢程式碼載入速度或增加程式記憶體使用,並且你需要匯入的程式碼被使用的可能性很低,或者你直到稍後才需要它時。
- 當你匯入的模組在載入時不存在時。
- 當匯入說明符字串需要動態構建時。(靜態匯入只支援靜態說明符。)
- 當被匯入的模組有副作用,並且你只希望在某個條件為真時才產生這些副作用時。(建議模組中不要有任何副作用,但有時你無法控制模組依賴項中的這種情況。)
- 當你處於非模組環境中時(例如,
eval或指令碼檔案)。
僅在必要時使用動態匯入。靜態形式更適合載入初始依賴項,並且可以更容易地從靜態分析工具和搖樹最佳化中受益。
如果你的檔案不作為模組執行(如果它在 HTML 檔案中被引用,script 標籤必須有 type="module"),你將無法使用靜態匯入宣告。另一方面,非同步的動態匯入語法始終可用,允許你將模組匯入到非模組環境中。
options 引數允許不同型別的匯入選項。例如,匯入屬性
import("./data.json", { with: { type: "json" } });
並非所有執行上下文都允許動態模組匯入。例如,import() 可以在主執行緒、共享工作執行緒或專用工作執行緒中使用,但如果在 service worker 或 worklet 中呼叫,則會丟擲錯誤。
模組名稱空間物件
模組名稱空間物件是一個描述模組所有匯出的物件。它是在模組評估時建立的靜態物件。有兩種方式可以訪問一個模組的名稱空間物件:透過名稱空間匯入(import * as name from moduleName),或者透過動態匯入的兌現值。
模組名稱空間物件是一個原型為 null 的密封物件。這意味著該物件的所有字串鍵都對應於模組的匯出,並且永遠不會有額外的鍵。所有鍵都按字典序可列舉(即 Array.prototype.sort() 的預設行為),預設匯出可作為一個名為 default 的鍵來訪問。此外,模組名稱空間物件有一個 [Symbol.toStringTag] 屬性,其值為 "Module",用於 Object.prototype.toString()。
當你使用 Object.getOwnPropertyDescriptors() 獲取其描述符時,字串屬性是不可配置且可寫的。然而,它們實際上是隻讀的,因為你不能給屬性重新賦一個新值。這種行為反映了靜態匯入建立“即時繫結”的事實——這些值可以由匯出它們的模組重新賦值,但不能由匯入它們的模組重新賦值。屬性的可寫性反映了值可能發生變化的可能性,因為不可配置且不可寫的屬性必須是常量。例如,你可以重新賦值一個匯出變數的值,並且新值可以在模組名稱空間物件中觀察到。
每個(規範化的)模組說明符對應一個唯一的模組名稱空間物件,所以以下通常為真
import * as mod from "/my-module.js";
import("/my-module.js").then((mod2) => {
console.log(mod === mod2); // true
});
除了一個奇特的情況:因為 promise 永遠不會兌現為一個 thenable,如果 my-module.js 模組匯出了一個名為 then() 的函式,那麼當動態匯入的 promise 被兌現時,該函式將作為 promise 解析過程的一部分被自動呼叫。
// my-module.js
export function then(resolve) {
console.log("then() called");
resolve(1);
}
// main.js
import * as mod from "/my-module.js";
import("/my-module.js").then((mod2) => {
// Logs "then() called"
console.log(mod === mod2); // false
});
警告: 不要從模組中匯出名為 then() 的函式。這會導致模組在動態匯入時的行為與靜態匯入時不同。
這種積極的快取確保了一段 JavaScript 程式碼永遠不會被執行超過一次,即使它被多次匯入。未來的匯入甚至不會導致 HTTP 請求或磁碟訪問。如果你確實需要重新匯入並重新評估一個模組而不重啟整個 JavaScript 環境,一個可能的技巧是在模組說明符中使用一個唯一的查詢引數。這在支援 URL 說明符的非瀏覽器執行時中也有效。
import(`/my-module.js?t=${Date.now()}`);
請注意,在一個長期執行的應用程式中,這可能導致記憶體洩漏,因為引擎無法安全地垃圾回收任何模組名稱空間物件。目前,沒有辦法手動清除模組名稱空間物件的快取。
模組名稱空間物件快取僅適用於成功載入和連結的模組。一個模組的匯入分三步:載入(獲取模組)、連結(主要是解析模組)和評估(執行解析後的程式碼)。只有評估失敗會被快取;如果一個模組載入或連結失敗,下一次匯入可能會再次嘗試載入和連結該模組。瀏覽器可能會也可能不會快取獲取操作的結果,但它應該遵循典型的 HTTP 語義,因此處理這類網路故障與處理 fetch() 故障沒有區別。
示例
僅為副作用匯入模組
(async () => {
if (somethingIsTrue) {
// import module for side effects
await import("/modules/my-module.js");
}
})();
如果你的專案使用匯出 ESM 的包,你也可以只為副作用匯入它們。這隻會執行包入口點檔案(以及它匯入的任何檔案)中的程式碼。
匯入預設值
如果你要解構匯入的模組名稱空間物件,那麼你必須重新命名 default 鍵,因為 default 是一個保留字。
(async () => {
if (somethingIsTrue) {
const {
default: myDefault,
foo,
bar,
} = await import("/modules/my-module.js");
}
})();
響應使用者操作按需匯入
這個例子展示瞭如何根據使用者操作(本例中為按鈕點選)將功能載入到頁面上,然後呼叫該模組中的一個函式。這不是實現此功能的唯一方法。import() 函式也支援 await。
const main = document.querySelector("main");
for (const link of document.querySelectorAll("nav > a")) {
link.addEventListener("click", (e) => {
e.preventDefault();
import("/modules/my-module.js")
.then((module) => {
module.loadPageInto(main);
})
.catch((err) => {
main.textContent = err.message;
});
});
}
根據環境匯入不同模組
在諸如伺服器端渲染之類的過程中,你可能需要在伺服器或瀏覽器上載入不同的邏輯,因為它們與不同的全域性變數或模組互動(例如,瀏覽器程式碼可以訪問像 document 和 navigator 這樣的 Web API,而伺服器程式碼可以訪問伺服器檔案系統)。你可以透過條件動態匯入來做到這一點。
let myModule;
if (typeof window === "undefined") {
myModule = await import("module-used-on-server");
} else {
myModule = await import("module-used-in-browser");
}
使用非字面量說明符匯入模組
動態匯入允許任何表示式作為模組說明符,而不必是字串字面量。
在這裡,我們併發載入 10 個模組,/modules/module-0.js、/modules/module-1.js 等,並呼叫每個模組匯出的 load 函式。
Promise.all(
Array.from({ length: 10 }).map(
(_, index) => import(`/modules/module-${index}.js`),
),
).then((modules) => modules.forEach((module) => module.load()));
規範
| 規範 |
|---|
| ECMAScript® 2026 語言規範 # sec-import-calls |
瀏覽器相容性
載入中…