diff --git a/files/zh-cn/web/javascript/reference/operators/import/index.md b/files/zh-cn/web/javascript/reference/operators/import/index.md new file mode 100644 index 00000000000000..1b685f551cc045 --- /dev/null +++ b/files/zh-cn/web/javascript/reference/operators/import/index.md @@ -0,0 +1,196 @@ +--- +title: import() +slug: Web/JavaScript/Reference/Operators/import +--- + +{{jsSidebar("Operators")}} + +通常被称为*动态导入*的 **`import()`** 语法是一个函数式表达式,它允许异步和动态地将 ECMAScript 模块加载到一个潜在的非模块环境中。 + +跟[与之对应的声明式语法](/zh-CN/docs/Web/JavaScript/Reference/Statements/import)不同,动态导入只有在被需要时才会求值,并提供了更强大的语法灵活性。 + +## 语法 + +```js-nolint +import(moduleName) +import(moduleName, options) +``` + +`import()` 调用是一个类似于函数调用的语法,但 `import` 本身是一个关键字,而不是一个函数。你不能像 `const myImport = import` 那样对其进行别名处理,这会抛出一个 {{jsxref("SyntaxError")}}。 + +[尾后逗号](/zh-CN/docs/Web/JavaScript/Reference/Trailing_commas) 只有在运行时也支持 `options` 时才被允许使用。请查看[浏览器兼容性](#[浏览器兼容性)部分。 + +### 参数 + +- `moduleName` + - : 要导入的模块。说明符的求值是宿主特异的(host-specified),但始终遵循与静态的 [import 声明](/zh-CN/docs/Web/JavaScript/Reference/Statements/import) 相同的算法。 +- `options` + - : 一个包含了导入选项的对象。以下是可识别的键: + - `with` + - : [import 属性](/zh-CN/docs/Web/JavaScript/Reference/Statements/import/with)。 + +### 返回值 + +返回一个 promise 对象: + +- 如果引用的模块被成功加载和求值,则兑现为一个[模块命名空间对象](#模块命名空间对象):一个包含 `moduleName` 中所有导出的对象。 +- 如果对 `moduleName` 做[字符串强制转换](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String#字符串强制转换)时抛出错误,则用抛出的错误作为拒绝原因。 +- 如果 `moduleName` 引用的模块不存在,则以实现定义的错误作为拒绝原因(Node 使用一个通用的 `Error`,而所有浏览器使用 `TypeError`)。 +- 如果引用的模块在求值时抛出错误,则用抛出的错误作为拒绝原因。 + +> **注意:** `import()` 从来不会同步地抛出错误。 + +## 描述 + +import 声明语法(`import something from "somewhere"`)是静态的,并且总是会在一加载时就对导入的模块求值。动态导入允许绕过 import 声明的语法刚性(syntactic rigidity),并有条件地或按需加载一个模块。以下是你可能需要使用动态导入的一些原因: + +- 当静态导入显著减慢你的代码加载,或增加你的程序内存使用时,那么你很可能不需要正要导入的代码,或者以后才会需要它。 +- 当你正要导入的模块在加载时并不存在时。 +- 当导入说明符字符串需要动态构建时。(静态导入仅支持静态说明符。) +- 当你正要导入的模块有副作用,并且你仅在某些条件下才希望有这些副作用。(建议模块中不要有任何副作用,但有时模块的依赖项中是否有副作用也无法控制) +- 当你处于非模块化的环境(例如,`eval` 或脚本文件)时。 + +仅在必要时使用动态导入。静态倒入更适合加载初始依赖项,并且可以更容易地从静态分析工具和[摇树优化](/zh-CN/docs/Glossary/Tree_shaking)中获益。 + +如果你的文件不是作为模块运行的(如果它在 HTML 文件中被引用,脚本标签必须有 `type="module"`),你将无法使用静态导入声明。而另一方面,异步的动态导入语法却始终可用,它允许你将模块导入到非模块环境中。 + +`options` 参数允许不同类型的导入选项。例如 [import 属性](/zh-CN/docs/Web/JavaScript/Reference/Statements/import/with): + +```js +import("./data.json", { with: { type: "json" } }); +``` + +动态模块导入并不在所有执行上下文中都可使用。例如,`import()` 可以在主线程、共享工作线程(shared worker)或专用工作线程(dedicated worker)中使用,但如果在 [service worker](/zh-CN/docs/Web/API/Service_Worker_API) 或 [worklet](/zh-CN/docs/Web/API/Worklet) 中调用,则会抛出错误。 + +### 模块命名空间对象 + +*模块命名空间对象*是一个描述模块所有导出的对象。它是一个静态对象,在模块被求值时创建。有两种方式可以访问模块的模块命名空间对象:通过[命名空间导入](/zh-CN/docs/Web/JavaScript/Reference/Statements/import#命名空间导入)(`import * as name from moduleName`)或通过动态导入的 promise 兑现值。 + +模块命名空间对象是一个[密封](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed)的对象,它具有 [null 原型对象](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object#null_原型对象)。也就是说,对象的所有字符串键对应于模块的导出,并且永远不会有额外的键。所有键都是以字典序[可枚举的](/zh-CN/docs/Web/JavaScript/Enumerability_and_ownership_of_properties)(即 [`Array.prototype.sort()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#描述) 的默认行为),默认导出名为 `default` 的键。此外,模块命名空间对象具有一个 [`[Symbol.toStringTag]`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag) 属性,值为 `"Module"`,在 {{jsxref("Object.prototype.toString()")}} 中被使用。 + +在使用 {{jsxref("Object.getOwnPropertyDescriptors()")}} 获取它们的描述符时,字符串属性是不可配置的(non-configurable)但可写的。然而,它们实际上是只读的(对于导入模块),因为你不能给属性重新赋一个新的值。这些值可以由导出它们的模块重新赋值,但不能由导入它们的模块重新赋值——这种行为反映了静态导入所创建的"[实时绑定](/zh-CN/docs/Web/JavaScript/Reference/Statements/import#导入的值只能由导出者修改)"。属性的可写入性反映了值变化的可能性,因为不可配置和不可写入的属性必须是常量。例如,你可以重新给一个变量的导出赋值,并且可以在模块命名空间对象中观察到新的值。 + +每个模块说明符对应一个唯一的模块命名空间对象,所以以下通常是正确的: + +```js +import * as mod from "/my-module.js"; + +import("/my-module.js").then((mod2) => { + console.log(mod === mod2); // true +}); +``` + +除了一个奇怪的情况:由于一个 promise 永远不会兑现为一个 [thenable](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenable),如果 `my-module.js` 模块恰好导出了一个名为 `then()` 的函数,那么该函数将在动态导入的 promise 兑现时自动被调用,因为这是 [resolve 函数](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise#resolve_函数)的一部分。 + +```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 +}); +``` + +> [!WARNING] +> 不要从模块中导出名为 `then()` 的函数。这将导致模块在动态导入和静态导入时的行为不同。 + +## 示例 + +### 仅导入模块以获取其副作用 + +```js +(async () => { + if (somethingIsTrue) { + // 导入模块以获取其副作用 + 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; + }); + }); +} +``` + +### 根据环境导入不同的模块 + +在服务器端渲染等过程中,你可能需要在服务器或浏览器中加载不同的逻辑,因为它们与不同的全局对象或模块交互(例如,浏览器代码可以访问 `document` 和 `navigator` 等 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())); +``` + +## 规范 + +{{Specifications}} + +## 浏览器兼容性 + +{{Compat}} + +## 参见 + +- [`import`](/zh-CN/docs/Web/JavaScript/Reference/Statements/import)