import()

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

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

语法

js
import(moduleName)
import(moduleName, options)

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

尾随逗号仅在运行时也支持 options 时才允许。请查看浏览器兼容性

参数

moduleName

要从中导入的模块。规范的评估是主机指定的,但始终遵循与静态导入声明相同的算法。

options

包含导入选项的对象。识别以下键

with

导入属性

返回值

返回一个 Promise,该 Promise

  • 如果引用的模块已成功加载和评估,则 fulfilled 到一个模块命名空间对象:一个包含 moduleName 中所有导出的对象。
  • 如果 moduleName强制转换为字符串抛出错误,则 rejected 并抛出该错误。
  • 如果 moduleName 指的是不存在的模块,则 rejected 并抛出一个实现定义的错误(Node 使用通用的 Error,而所有浏览器都使用 TypeError)。
  • 如果引用的模块的评估抛出错误,则 rejected 并抛出该错误。

注意:import() 永远不会同步抛出错误。

描述

导入声明语法 (import something from "somewhere") 是静态的,并且始终会导致导入的模块在加载时进行评估。动态导入允许绕过导入声明的语法刚性,并根据条件或按需加载模块。以下是一些可能需要使用动态导入的原因

  • 当静态导入显着减慢代码加载速度或增加程序内存使用量,并且您不太可能需要导入的代码,或者您在稍后才会需要它时。
  • 当您要导入的模块在加载时不存在时。
  • 当需要动态构建导入规范字符串时。(静态导入仅支持静态规范。)
  • 当要导入的模块具有副作用,并且您不希望这些副作用除非某个条件为真。(建议模块不要有任何副作用,但有时您无法控制模块依赖项中的这种情况。)
  • 当您处于非模块环境(例如,eval 或脚本文件)中时。

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

如果您的文件未作为模块运行(如果它在 HTML 文件中引用,则 script 标记必须具有 type="module"),则您将无法使用静态导入声明。另一方面,异步动态导入语法始终可用,允许您将模块导入非模块环境。

options 参数允许不同类型的导入选项。例如,导入属性

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

动态模块导入并非在所有执行上下文中都允许。例如,import() 可用于主线程、共享工作线程或专用工作线程,但如果在服务工作线程worklet中调用,则会抛出错误。

模块命名空间对象

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

模块命名空间对象是一个密封的对象,具有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 永远不会 fulfilled 到thenable,如果 my-module.js 模块导出一个名为 then() 的函数,则在动态导入的 Promise fulfilled 时,该函数将自动被调用,作为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() 的函数。这将导致模块在动态导入时与静态导入时的行为不同。

示例

仅为其副作用导入模块

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 语言规范
# sec-import-calls

浏览器兼容性

BCD 表格仅在浏览器中加载

另请参阅