import
Baseline 广泛可用 *
静态的 import 声明用于导入由另一个模块导出的只读的实时绑定。导入的绑定被称为实时绑定,因为它们由导出该绑定的模块更新,但不能被导入模块重新赋值。
为了在源文件中使用 import 声明,运行时必须将该文件解释为模块。在 HTML 中,这是通过向 <script> 标签添加 type="module" 来实现的。模块会自动在严格模式下解释。
还有一种类似函数的动态 import(),它不需要 type="module" 脚本。
语法
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
defaultExport-
将引用模块中默认导出的名称。必须是有效的 JavaScript 标识符。
module-name-
要导入的模块。只允许使用单引号和双引号的字符串字面量。指定符的评估由宿主指定。大多数宿主与浏览器保持一致,将指定符解析为相对于当前模块 URL 的 URL(参见
import.meta.url)。Node、打包工具和其他非浏览器环境通常在此基础上定义自己的功能,因此您应该查阅它们的文档以了解确切的规则。模块说明符解析部分也有更多信息。 name-
模块对象的名称,在引用导入时将用作一种命名空间。必须是有效的 JavaScript 标识符。
exportN-
要导入的导出名称。名称可以是标识符或字符串字面量,具体取决于
module-name声明要导出的内容。如果它是字符串字面量,则必须将其别名为有效的标识符。 aliasN-
将引用命名导入的名称。必须是有效的 JavaScript 标识符。
"module-name" 后面可以跟一组导入属性,以 with 关键字开头。
描述
import 声明只能出现在模块中,并且只能在顶层(即,不能在块、函数等内部)。如果在非模块上下文(例如,没有 type="module" 的 <script> 标签、eval、new Function,它们都将“脚本”或“函数体”作为解析目标)中遇到 import 声明,则会抛出 SyntaxError。要在非模块上下文中加载模块,请改用动态导入语法。
所有导入的绑定不能与任何其他声明处于同一作用域,包括 let、const、class、function、var 和 import 声明。
import 声明被设计为语法严格(例如,只允许字符串字面量指定符,只允许在顶层使用,所有绑定必须是标识符),这允许模块在评估之前进行静态分析和链接。这是使模块本质上异步的关键,并支持顶层 await 等功能。
导入声明的形式
有四种形式的 import 声明
- 命名导入:
import { export1, export2 } from "module-name"; - 默认导入:
import defaultExport from "module-name"; - 命名空间导入:
import * as name from "module-name"; - 副作用导入:
import "module-name";
以下示例旨在阐明语法。
命名导入
给定一个名为 myExport 的值,它已从模块 my-module 隐式地作为 export * from "another.js" 或显式地使用 export 语句导出,这会将 myExport 插入当前作用域。
import { myExport } from "/modules/my-module.js";
您可以从同一个模块导入多个名称。
import { foo, bar } from "/modules/my-module.js";
您可以在导入时重命名导出。例如,这会将 shortName 插入当前作用域。
import { reallyReallyLongModuleExportName as shortName } from "/modules/my-module.js";
模块还可以将成员作为字符串字面量导出,该字符串字面量不是有效的标识符,在这种情况下,您必须将其别名化才能在当前模块中使用。
// /modules/my-module.js
const a = 1;
export { a as "a-b" };
import { "a-b" as a } from "/modules/my-module.js";
注意: import { x, y } from "mod" 不等同于 import defaultExport from "mod" 然后从 defaultExport 中解构 x 和 y。命名导入和默认导入在 JavaScript 模块中是不同的语法。
默认导入
默认导出需要使用相应的默认导入语法进行导入。此版本直接导入默认值。
import myDefault from "/modules/my-module.js";
由于默认导出没有显式指定名称,因此您可以为标识符指定任何您喜欢的名称。
还可以使用命名空间导入或命名导入来指定默认导入。在这种情况下,默认导入必须首先声明。例如:
import myDefault, * as myModule from "/modules/my-module.js";
// myModule.default and myDefault point to the same binding
or
import myDefault, { foo, bar } from "/modules/my-module.js";
导入名为 default 的名称与默认导入具有相同的效果。由于 default 是一个保留字,因此需要将其别名化。
import { default as myDefault } from "/modules/my-module.js";
命名空间导入
以下代码将 myModule 插入当前作用域,其中包含来自 /modules/my-module.js 模块的所有导出。
import * as myModule from "/modules/my-module.js";
在这里,myModule 代表一个命名空间对象,其中包含所有导出作为属性。例如,如果上面导入的模块包含导出 doAllTheAmazingThings(),您将像这样调用它:
myModule.doAllTheAmazingThings();
myModule 是一个 密封 对象,具有 null 原型。默认导出以名为 default 的键的形式提供。有关更多信息,请参阅模块命名空间对象。
注意: JavaScript 没有像 import * from "module-name" 这样的通配符导入,因为名称冲突的可能性很高。
只为副作用导入模块
只为副作用导入整个模块,而不导入任何内容。这会运行模块的全局代码,但实际上不导入任何值。
import "/modules/my-module.js";
这通常用于polyfill,它们会修改全局变量。
提升
导入声明是提升的。在这种情况下,这意味着导入引入的标识符在整个模块作用域中都可用,并且它们的副作用在模块的其余代码运行之前产生。
myModule.doAllTheAmazingThings(); // myModule.doAllTheAmazingThings is imported by the next line
import * as myModule from "/modules/my-module.js";
模块指定符解析
ECMAScript 规范没有定义模块指定符如何解析,并将其留给宿主环境(例如,浏览器、Node.js、Deno)。浏览器行为由HTML 规范指定,这已成为所有环境的事实标准。
HTML 规范、Node 和许多其他规范都广泛识别三种类型的指定符:
- 以
/、./或../开头的相对指定符,它们相对于当前模块 URL 解析。 - 可解析为 URL 的绝对指定符,它们按原样解析。
- 不是上述任何一种的裸指定符。
相对指定符最值得注意的注意事项,特别是对于熟悉 CommonJS 约定的人来说,是浏览器禁止一个指定符隐式解析为许多潜在的候选。在 CommonJS 中,如果您有 main.js 和 utils/index.js,那么以下所有内容都将从 utils/index.js 导入“默认导出”:
// main.js
const utils = require("./utils"); // Omit the "index.js" file name
const utils = require("./utils/index"); // Omit only the ".js" extension
const utils = require("./utils/index.js"); // The most explicit form
在 Web 上,这代价很高,因为如果您编写 import x from "./utils",浏览器需要向 utils、utils/index.js、utils.js 以及可能许多其他 URL 发送请求,直到找到可导入的模块。因此,在 HTML 规范中,指定符默认只能是相对于当前模块 URL 解析的 URL。您不能省略文件扩展名或 index.js 文件名。此行为已由 Node 的 ESM 实现继承,但它不是 ECMAScript 规范的一部分。
请注意,这并不意味着 import x from "./utils" 在 Web 上永远不起作用。浏览器仍然会向该 URL 发送请求,如果服务器能返回正确的内容,导入就会成功。这要求服务器实现一些自定义解析逻辑,因为通常没有扩展名的请求被理解为 HTML 文件的请求。
绝对指定符可以是任何类型的URL,可解析为可导入的源代码。最值得注意的是:
-
HTTP URL 在 Web 上始终受支持,因为大多数脚本已经具有 HTTP URL。Deno 本身就支持它(它最初将整个模块系统都建立在 HTTP URL 上),但它在 Node 中仅通过自定义 HTTPS 加载器获得实验性支持。
-
file:URL 受许多非浏览器运行时(如 Node)支持,因为那里的脚本已经具有file:URL,但出于安全原因,浏览器不支持它们。 -
数据 URL 受包括浏览器、Node、Deno 等在内的许多运行时支持。它们对于将小型模块直接嵌入到源代码中非常有用。支持的 MIME 类型是那些指定可导入源代码的类型,例如 JavaScript 的
text/javascript、JSON 模块的application/json、WebAssembly 模块的application/wasm等。(它们可能仍需要导入属性。)js// HTTP URLs import x from "https://example.com/x.js"; // Data URLs import x from "data:text/javascript,export default 42;"; // Data URLs for JSON modules import x from 'data:application/json,{"foo":42}' with { type: "json" };text/javascript数据 URL 仍被解释为模块,但它们不能使用相对导入——因为data:URL 方案不是分层的。也就是说,import x from "data:text/javascript,import y from './y.js';"将会抛出错误,因为相对指定符'./y.js'无法解析。 -
node:URL 解析为内置的 Node.js 模块。它们受 Node 和其他声称与 Node 兼容的运行时(如 Bun)支持。
裸指定符,由 CommonJS 推广,在 node_modules 目录中解析。例如,如果您有 import x from "foo",则运行时将在当前模块的父目录中的任何 node_modules 目录中查找 foo 包。此行为可以通过使用导入映射在浏览器中重现,导入映射还允许您以其他方式自定义解析。
模块解析算法也可以使用 HTML 规范定义的 import.meta.resolve 函数以编程方式执行。
示例
标准导入
在此示例中,我们创建一个可重用模块,该模块导出一个函数,用于获取给定范围内的所有素数。
// getPrimes.js
/**
* Returns a list of prime numbers that are smaller than `max`.
*/
export function getPrimes(max) {
const isPrime = Array.from({ length: max }, () => true);
isPrime[0] = isPrime[1] = false;
isPrime[2] = true;
for (let i = 2; i * i < max; i++) {
if (isPrime[i]) {
for (let j = i ** 2; j < max; j += i) {
isPrime[j] = false;
}
}
}
return [...isPrime.entries()]
.filter(([, isPrime]) => isPrime)
.map(([number]) => number);
}
import { getPrimes } from "/modules/getPrimes.js";
console.log(getPrimes(10)); // [2, 3, 5, 7]
导入的值只能由导出者修改
被导入的标识符是实时绑定,因为导出它的模块可以重新赋值它,并且导入的值会改变。但是,导入它的模块不能重新赋值它。尽管如此,任何持有导出对象的模块都可以修改该对象,并且所有其他导入相同值的模块都可以观察到修改后的值。
您还可以通过模块命名空间对象观察新值。
// my-module.js
export let myValue = 1;
setTimeout(() => {
myValue = 2;
}, 500);
// main.js
import { myValue } from "/modules/my-module.js";
import * as myModule from "/modules/my-module.js";
console.log(myValue); // 1
console.log(myModule.myValue); // 1
setTimeout(() => {
console.log(myValue); // 2; my-module has updated its value
console.log(myModule.myValue); // 2
myValue = 3; // TypeError: Assignment to constant variable.
// The importing module can only read the value but can't re-assign it.
}, 1000);
规范
| 规范 |
|---|
| ECMAScript® 2026 语言规范 # sec-imports |
浏览器兼容性
加载中…
另见
exportimport()import.meta- 导入断言
- 在 blogs.windows.com 上预览 ES6 模块以及 ES2015、ES2016 及更高版本(2016 年)
- hacks.mozilla.org 上的 ES6 深入:模块 (2015)
- hacks.mozilla.org 上的 ES 模块:卡通深度剖析 (2018)
- Axel Rauschmayer 博士的 探索 JS,第 16 章:模块
- 在 javascript.info 上导出和导入