import()

Baseline 廣泛可用 *

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2020 年 1 月⁩ 起,所有主流瀏覽器均已支援。

* 此特性的某些部分可能存在不同級別的支援。

import() 語法,通常稱為*動態匯入*,是一個類似函式的表示式,允許非同步和動態地將一個 ECMAScript 模組載入到一個可能非模組的環境中。

宣告式匯入不同,動態匯入只在需要時才被評估,並允許更大的語法靈活性。

語法

js
import(moduleName)
import(moduleName, options)

import() 呼叫是一種非常類似於函式呼叫的語法,但 import 本身是一個關鍵字,而不是一個函式。你不能像 const myImport = import 那樣給它起別名,這樣做會丟擲一個 SyntaxError

只有當執行時也支援 options 引數時,才允許使用尾隨逗號。請檢查瀏覽器相容性

引數

moduleName

要匯入的模組。模組說明符的評估由宿主環境指定,但總是遵循與靜態 import 宣告相同的演算法。

options

一個包含匯入選項的物件。識別以下鍵

with

匯入屬性

返回值

返回一個 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 引數允許不同型別的匯入選項。例如,匯入屬性

js
import("./data.json", { with: { type: "json" } });

並非所有執行上下文都允許動態模組匯入。例如,import() 可以在主執行緒、共享工作執行緒或專用工作執行緒中使用,但如果在 service workerworklet 中呼叫,則會丟擲錯誤。

模組名稱空間物件

模組名稱空間物件是一個描述模組所有匯出的物件。它是在模組評估時建立的靜態物件。有兩種方式可以訪問一個模組的名稱空間物件:透過名稱空間匯入import * as name from moduleName),或者透過動態匯入的兌現值。

模組名稱空間物件是一個原型為 null密封物件。這意味著該物件的所有字串鍵都對應於模組的匯出,並且永遠不會有額外的鍵。所有鍵都按字典序可列舉(即 Array.prototype.sort() 的預設行為),預設匯出可作為一個名為 default 的鍵來訪問。此外,模組名稱空間物件有一個 [Symbol.toStringTag] 屬性,其值為 "Module",用於 Object.prototype.toString()

當你使用 Object.getOwnPropertyDescriptors() 獲取其描述符時,字串屬性是不可配置且可寫的。然而,它們實際上是隻讀的,因為你不能給屬性重新賦一個新值。這種行為反映了靜態匯入建立“即時繫結”的事實——這些值可以由匯出它們的模組重新賦值,但不能由匯入它們的模組重新賦值。屬性的可寫性反映了值可能發生變化的可能性,因為不可配置且不可寫的屬性必須是常量。例如,你可以重新賦值一個匯出變數的值,並且新值可以在模組名稱空間物件中觀察到。

每個(規範化的)模組說明符對應一個唯一的模組名稱空間物件,所以以下通常為真

js
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 解析過程的一部分被自動呼叫。

js
// my-module.js
export function then(resolve) {
  console.log("then() called");
  resolve(1);
}
js
// 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 說明符的非瀏覽器執行時中也有效。

js
import(`/my-module.js?t=${Date.now()}`);

請注意,在一個長期執行的應用程式中,這可能導致記憶體洩漏,因為引擎無法安全地垃圾回收任何模組名稱空間物件。目前,沒有辦法手動清除模組名稱空間物件的快取。

模組名稱空間物件快取僅適用於成功載入和連結的模組。一個模組的匯入分三步:載入(獲取模組)、連結(主要是解析模組)和評估(執行解析後的程式碼)。只有評估失敗會被快取;如果一個模組載入或連結失敗,下一次匯入可能會再次嘗試載入和連結該模組。瀏覽器可能會也可能不會快取獲取操作的結果,但它應該遵循典型的 HTTP 語義,因此處理這類網路故障與處理 fetch() 故障沒有區別。

示例

僅為副作用匯入模組

js
(async () => {
  if (somethingIsTrue) {
    // import module for side effects
    await import("/modules/my-module.js");
  }
})();

如果你的專案使用匯出 ESM 的包,你也可以只為副作用匯入它們。這隻會執行包入口點檔案(以及它匯入的任何檔案)中的程式碼。

匯入預設值

如果你要解構匯入的模組名稱空間物件,那麼你必須重新命名 default 鍵,因為 default 是一個保留字。

js
(async () => {
  if (somethingIsTrue) {
    const {
      default: myDefault,
      foo,
      bar,
    } = await import("/modules/my-module.js");
  }
})();

響應使用者操作按需匯入

這個例子展示瞭如何根據使用者操作(本例中為按鈕點選)將功能載入到頁面上,然後呼叫該模組中的一個函式。這不是實現此功能的唯一方法。import() 函式也支援 await

js
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;
      });
  });
}

根據環境匯入不同模組

在諸如伺服器端渲染之類的過程中,你可能需要在伺服器或瀏覽器上載入不同的邏輯,因為它們與不同的全域性變數或模組互動(例如,瀏覽器程式碼可以訪問像 documentnavigator 這樣的 Web API,而伺服器程式碼可以訪問伺服器檔案系統)。你可以透過條件動態匯入來做到這一點。

js
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 函式。

js
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

瀏覽器相容性

另見