基本的导入
一旦你有了包含导出的模块,就能在其他模块内使用 import 关键字来访问已被导出的功能。import 语句有两个部分,一是需要导入的标识符,二是需导入的标识符的来源模块。此处是导入语句的基本形式:
import { identifier1, identifier2 } from "./example.js";
在 import 之后的花括号指明了从给定模块导入对应的绑定,from 关键字则指明了需要导入的模块。模块由一个表示模块路径的字符串(被称为模块说明符,module specifier)来指定。在浏览器环境中导入模块,使用与 <script> 元素相同的路径格式,这表示你必须在其中包含文件扩展名。而另一方面 Node.js 则遵循了它的传统惯例,基于文件系统前缀来分辨本地文件与包(package),例如,example 代表一个包,而 ./example.js 则代表一个本地文件。
导入绑定的列表看起来与对象解构相似,但实则并无关联。
当从模块导入了一个绑定时,该绑定表现得就像使用了 const 的定义。这意味着你不能再定义另一个同名变量(包括导入另一个同名绑定),也不能在对应的 import 语句之前使用此标识符(也就是要受暂时性死区限制),更不能修改它的值。
导入单个绑定
对于 “基本的导入” 小节的第一个例子,先假设它位于一个文件名为 example.js 的模块内。你能用多种方式来导入并使用来自该模块的绑定。例如,你可以仅导入一个标识符:
// import just one
import { sum } from "./example.js";
console.log(sum(1, 2)); // 3
sum = 1; // error
尽管 example.js 并非只导出了一个 sum() 函数,但本例仅仅导入了此函数。 假若你尝试给 sum 赋一个新值,由于不允许对已导入的绑定重新赋值,于是就会导致错误。
要确保在导入的文件名前面使用 /、./ 或 ../,以便在浏览器与 Node.js 之间保持良好兼容性。 |
导入多个绑定
如果你想从 example 模块导入多个绑定,你可以像下面这样显式的列出它们:
// import multiple
import { sum, multiply, magicNumber } from "./example.js";
console.log(sum(1, magicNumber)); // 8
console.log(multiply(1, 2)); // 2
此处从 example 模块导入了三个绑定:sum、multiply 与 magicNumber,之后便可以使用它们,仿佛它们是在当前模块中被定义的。
完全导入一个模块
还有一种特殊情况,即允许你将整个模块当作单一对象进行导入,该模块的所有导出都会作为对象的属性存在。例如:
// import everything
import * as example from "./example.js";
console.log(example.sum(1,
example.magicNumber)); // 8
console.log(example.multiply(1, 2)); // 2
在此代码中,example.js 中所有导出的绑定都被加载到一个名为 example 的对象中,具名导出(sum() 函数、 multiple() 函数与 magicNumber)都成为 example 的可用属性。这种导入格式被称为命名空间导入(namespace import),这是因为该 example 对象并不存在于 example.js 文件中,而是作为一个命名空间对象被创建使用,其中包含了 example.js 的所有导出成员。
然而要记住,无论你对同一个模块使用了多少次 import 语句,该模块都只会被执行一次。在导出模块的代码执行之后,已被实例化的模块就被保留在内存中,并随时都能被其他 import 所引用。研究以下例子:
import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";
尽管此处的模块使用了三个 import 语句,但 example.js 只会被执行一次。若同一个应用中的其他模块打算从 example.js 导入绑定,则那些模块都会使用这段代码中所用的同一个模块实例。
模块语法的限制(Module Syntax Limitations) export 与 import 都有一个重要的限制,那就是它们必须被用在其他语句或表达式的外部。例如,以下代码有语法错误:
此处的 export 语句位于一个 if 语句内部,这是不被许可的。导出语句不能是有条件的,也不能以任何方式动态使用。原因之一是模块语法需要让 JS 能静态判断需要导出什么,正因为此,你只能在模块的顶级作用域使用 export 。 类似的,你不能在一个语句内部使用 import,也只能将其用在顶级作用域。这意味着以下代码也有语法错误:
出于与不能动态导出绑定相同的原因,你也不能动态导入绑定。export 与 import 关键字被设计为静态的,以便让诸如文本编辑器之类的工具能轻易判断模块有哪些信息可用。 |
导入绑定的一个微妙怪异点
ES6 的 import 语句为变量、函数与类创建了只读绑定,而不像普通变量那样简单引用了原始绑定。尽管导入绑定的模块无法修改绑定的值,但负责导出的模块却能做到这一点。例如,假设你想要使用以下模块:
export var name = "Nicholas";
export function setName(newName) {
name = newName;
}
当你导入了这两个绑定后,setName() 函数还可以改变 name 的值:
import { name, setName } from "./example.js";
console.log(name); // "Nicholas"
setName("Greg");
console.log(name); // "Greg"
name = "Nicholas"; // error
调用 setName("Greg") 会回到导出 setName() 的模块内部,并在那里执行, 从而将 name 设置为 "Greg"。注意这个变化会自动反映到所导入的 name 绑定上,这是因为绑定的 name 是导出的 name 标识符的本地名称,二者并非同一个事物。
译注:对本小节内容进行补充说明
在此代码中,变量 b 开始时对变量 a 进行了一个 “引用”,但只是将 a 的值拷贝了一份。如果对变量 a 的值进行修改,变量 b 的值是不会随着变化的。 而在范例中的模块导入与导出,外部模块导入的 name 变量与在 example.js 模块内部的 name 变量对比,前者是对于后者的只读引用,会始终反映出后者的变化。就算后者的值在负责导出的模块中发生了变化,这种绑定关系也不会被破坏。模块导出与导入的绑定机制,与写在一个文件或模块内的代码是不同的。 |