import()

import() 语法通常称为动态导入,是一种类似函数的表达式,允许将 ECMAScript 模块异步动态加载到潜在的非模块环境中。

¥The import() syntax, commonly called dynamic import, is a function-like expression that allows loading an ECMAScript module asynchronously and dynamically into a potentially non-module environment.

声明式对应 不同,动态导入仅在需要时进行评估,并允许更大的语法灵活性。

¥Unlike the declaration-style counterpart, dynamic imports are only evaluated when needed, and permit greater syntactic flexibility.

语法

¥Syntax

js
import(moduleName)

import() 调用是一种与函数调用非常相似的语法,但 import 本身是一个关键字,而不是一个函数。你不能像 const myImport = import 那样给它起别名,这会抛出 SyntaxError

¥The import() call is a syntax that closely resembles a function call, but import itself is a keyword, not a function. You cannot alias it like const myImport = import, which will throw a SyntaxError.

参数

¥Parameters

moduleName

要从中导入的模块。说明符的评估是由主机指定的,但始终遵循与 static 导入报关 相同的算法。

返回值

¥Return value

返回履行 模块命名空间对象 的承诺:包含 moduleName 的所有导出的对象。

¥Returns a promise which fulfills to a module namespace object: an object containing all exports from moduleName.

import() 的评估永远不会同步抛出错误。moduleName强制为字符串,如果强制抛出,则 Promise 会被拒绝并抛出错误。

¥The evaluation of import() never synchronously throws an error. moduleName is coerced to a string, and if coercion throws, the promise is rejected with the thrown error.

描述

¥Description

导入声明语法 (import something from "somewhere") 是静态的,并且始终会导致导入的模块在加载时被评估。动态导入允许人们规避导入声明的语法刚性,并有条件或按需加载模块。以下是你可能需要使用动态导入的一些原因:

¥The import declaration syntax (import something from "somewhere") is static and will always result in the imported module being evaluated at load time. Dynamic imports allow one to circumvent the syntactic rigidity of import declarations and load a module conditionally or on demand. The following are some reasons why you might need to use dynamic import:

  • 静态导入会显着减慢代码的加载速度或增加程序的内存使用量,并且你需要正在导入的代码的可能性很低,或者直到稍后才需要它。
  • 当你导入的模块在加载时不存在时。
  • 当需要动态构造导入说明符字符串时。(静态导入仅支持静态说明符。)
  • 当导入的模块有副作用时,除非某些条件成立,否则你不希望出现这些副作用。(建议不要在模块中产生任何副作用,但有时你无法在模块依赖中控制这一点。)
  • 当你处于非模块环境(例如 eval 或脚本文件)时。

仅在必要时使用动态导入。静态形式更适合加载初始依赖,并且可以更容易地从静态分析工具和 摇树 中受益。

¥Use dynamic import only when necessary. The static form is preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking.

如果你的文件不是作为模块运行(如果在 HTML 文件中引用它,则脚本标记必须具有 type="module"),你将无法使用静态导入声明,但异步动态导入语法将始终可用,允许你 将模块导入到非模块环境中。

¥If your file is not run as a module (if it's referenced in an HTML file, the script tag must have type="module"), you will not be able to use static import declarations, but the asynchronous dynamic import syntax will always be available, allowing you to import modules into non-module environments.

动态模块导入不允许在所有执行上下文中使用。例如,import() 可以在主线程、共享工作线程或专用工作线程中使用,但如果在 服务工作进程worklet 中调用,则会抛出异常。

¥Dynamic module import is not permitted in all execution contexts. For example, import() can be used in the main thread, a shared worker, or a dedicated worker, but will throw if called within a service worker or a worklet.

模块命名空间对象

¥Module namespace object

模块命名空间对象是描述模块的所有导出的对象。它是在评估模块时创建的静态对象。有两种方法可以访问模块的模块命名空间对象:通过 命名空间导入 (import * as name from moduleName),或通过动态导入的履行值。

¥A module namespace object is an object that describes all exports from a module. It is a static object that is created when the module is evaluated. There are two ways to access the module namespace object of a module: through a namespace import (import * as name from moduleName), or through the fulfillment value of a dynamic import.

模块命名空间对象是带有 null 原型机sealed 对象。这意味着对象的所有字符串键都对应于模块的导出,并且永远不会有额外的键。所有键均按字典顺序排列为 enumerable(即 Array.prototype.sort() 的默认行为),默认导出可用作名为 default 的键。此外,模块命名空间对象有一个 @@toStringTag 属性,其值为 "Module",在 Object.prototype.toString() 中使用。

¥The module namespace object is a sealed object with null prototype. This means all string keys of the object correspond to the exports of the module and there are never extra keys. All keys are enumerable in lexicographic order (i.e. the default behavior of Array.prototype.sort()), with the default export available as a key called default. In addition, the module namespace object has a @@toStringTag property with the value "Module", used in Object.prototype.toString().

当你使用 Object.getOwnPropertyDescriptors() 获取字符串属性的描述符时,字符串属性是不可配置和可写的。但是,它们实际上是只读的,因为你无法将属性重新分配给新值。此行为反映了静态导入创建“实时绑定”的事实 - 这些值可以由导出它们的模块重新分配,但不能由导入它们的模块重新分配。属性的可写性反映了值更改的可能性,因为不可配置和不可写的属性必须是常量。例如,你可以重新分配变量的导出值,并且可以在模块命名空间对象中观察到新值。

¥The string properties are non-configurable and writable when you use Object.getOwnPropertyDescriptors() to get their descriptors. However, they are effectively read-only, because you cannot re-assign a property to a new value. This behavior mirrors the fact that static imports create "live bindings" — the values can be re-assigned by the module exporting them, but not by the module importing them. The writability of the properties reflects the possibility of the values changing, because non-configurable and non-writable properties must be constant. For example, you can re-assign the exported value of a variable, and the new value can be observed in the module namespace object.

每个模块说明符对应一个唯一的模块命名空间对象,因此以下情况通常是正确的:

¥Each module specifier corresponds to a unique module namespace object, so the following is generally true:

js
import * as mod from "/my-module.js";

import("/my-module.js").then((mod2) => {
  console.log(mod === mod2); // true
});

除了一个奇怪的例子:因为承诺永远不会履行到 thenable,如果 my-module.js 模块导出一个名为 then() 的函数,则当动态导入的承诺履行时,该函数将自动被调用,作为 承诺决议 过程的一部分。

¥Except in one curious case: because a promise never fulfills to a thenable, if the my-module.js module exports a function called then(), that function will automatically get called when the dynamic import's promise is fulfilled, as part of the promise resolution process.

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() 的函数。这将导致模块在动态导入时与静态导入时的行为不同。

¥Warning: Do not export a function called then() from a module. This will cause the module to behave differently when imported dynamically than when imported statically.

示例

¥Examples

导入模块仅用于其副作用

¥Import a module for its side effects only

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

如果你的项目使用导出 ESM 的包,你也可以仅出于副作用而导入它们。这将仅运行包入口点文件(及其导入的任何文件)中的代码。

¥If your project uses packages that export ESM, you can also import them for side effects only. This will run the code in the package entry point file (and any files it imports) only.

导入默认值

¥Importing defaults

你需要从返回的对象中解构并重命名 "default" 键。

¥You need to destructure and rename the "default" key from the returned object.

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

根据用户操作按需导入

¥Importing on-demand in response to user action

此示例演示如何根据用户操作(在本例中为按钮单击)将功能加载到页面上,然后调用该模块内的函数。这不是实现此功能的唯一方法。import() 功能也支持 await

¥This example shows how to load functionality on to a page based on a user action, in this case a button click, and then call a function within that module. This is not the only way to implement this functionality. The import() function also supports 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;
      });
  });
}

根据环境导入不同的模块

¥Importing different modules based on environment

在服务器端渲染等过程中,你可能需要在服务器或浏览器中加载不同的逻辑,因为它们与不同的全局变量或模块交互(例如,浏览器代码可以访问 documentnavigator 等 Web API,而服务器代码可以访问 到服务器文件系统)。你可以通过条件动态导入来执行此操作。

¥In processes such as server-side rendering, you may need to load different logic on server or in browser because they interact with different globals or modules (for example, browser code has access to web APIs like document and navigator, while server code has access to the server file system). You can do so through a conditional dynamic import.

js
let myModule;

if (typeof window === "undefined") {
  myModule = await import("module-used-on-server");
} else {
  myModule = await import("module-used-in-browser");
}

使用非文字说明符导入模块

¥Importing modules with a non-literal specifier

动态导入允许任何表达式作为模块说明符,不一定是字符串文字。

¥Dynamic imports allow any expression as the module specifier, not necessarily string literals.

这里,我们同时加载 /modules/module-0.js/modules/module-1.js 等 10 个模块,并调用每个模块导出的 load 函数。

¥Here, we load 10 modules, /modules/module-0.js, /modules/module-1.js, etc., concurrently, and call the load functions that each one exports.

js
Promise.all(
  Array.from({ length: 10 }).map(
    (_, index) => import(`/modules/module-${index}.js`),
  ),
).then((modules) => modules.forEach((module) => module.load()));

规范

Specification
ECMAScript Language Specification
# sec-import-calls

¥Specifications

浏览器兼容性

BCD tables only load in the browser

¥Browser compatibility

也可以看看

¥See also