import()
**import()
** 语法,通常称为动态导入,是一个类似函数的表达式,允许异步和动态地将 ECMAScript 模块加载到可能是非模块的环境中。
与声明式对应部分不同,动态导入仅在需要时才进行评估,并且允许更大的语法灵活性。
语法
import(moduleName)
import(moduleName, options)
import()
调用是一种语法,它与函数调用非常相似,但 import
本身是一个关键字,而不是函数。您不能像 const myImport = import
那样为它设置别名,这将抛出一个SyntaxError
。
参数
moduleName
-
要从中导入的模块。规范的评估是主机指定的,但始终遵循与静态导入声明相同的算法。
options
-
包含导入选项的对象。识别以下键
返回值
描述
导入声明语法 (import something from "somewhere"
) 是静态的,并且始终会导致导入的模块在加载时进行评估。动态导入允许绕过导入声明的语法刚性,并根据条件或按需加载模块。以下是一些可能需要使用动态导入的原因
- 当静态导入显着减慢代码加载速度或增加程序内存使用量,并且您不太可能需要导入的代码,或者您在稍后才会需要它时。
- 当您要导入的模块在加载时不存在时。
- 当需要动态构建导入规范字符串时。(静态导入仅支持静态规范。)
- 当要导入的模块具有副作用,并且您不希望这些副作用除非某个条件为真。(建议模块不要有任何副作用,但有时您无法控制模块依赖项中的这种情况。)
- 当您处于非模块环境(例如,
eval
或脚本文件)中时。
仅在必要时使用动态导入。静态形式更适合加载初始依赖项,并且可以更容易地从静态分析工具和tree shaking中受益。
如果您的文件未作为模块运行(如果它在 HTML 文件中引用,则 script 标记必须具有 type="module"
),则您将无法使用静态导入声明。另一方面,异步动态导入语法始终可用,允许您将模块导入非模块环境。
options
参数允许不同类型的导入选项。例如,导入属性
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()
获取其描述符时,字符串属性是不可配置且可写的。但是,它们实际上是只读的,因为您不能将属性重新分配给新值。此行为反映了静态导入创建“实时绑定”这一事实——值可以由导出它们的模块重新分配,但不能由导入它们的模块重新分配。属性的可写性反映了值可能发生变化的可能性,因为不可配置且不可写的属性必须是常量。例如,您可以重新分配变量的导出值,并且可以在模块命名空间对象中观察到新值。
每个模块规范都对应一个唯一的模块命名空间对象,因此以下内容通常为真
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 解析过程的一部分。
// 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()
的函数。这将导致模块在动态导入时与静态导入时的行为不同。
示例
仅为其副作用导入模块
(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 语言规范 # sec-import-calls |
浏览器兼容性
BCD 表格仅在浏览器中加载