我们来详细解析 JavaScript ES6 模块中的 export 和 import。
export 和 import 是 ES6 模块系统的核心,它们使得 JavaScript 代码可以被分割成可重用的、独立的文件(模块),极大地改善了代码的组织结构、可维护性和依赖管理。
核心概念
-
模块化: 每个文件就是一个独立的模块。
-
独立作用域: 每个模块内部都有自己的作用域,模块内的变量、函数、类在外部默认是不可见的。
-
静态解析:
import和export命令只能在模块的顶层使用,不能在代码块(如if语句)或函数内部使用。这是因为 ES6 模块在编译时(静态地)就能确定模块的依赖关系,以及导入和导出了哪些变量。
export:从模块中导出
export 命令用于规定模块的对外接口。一个模块可以有多个 export。导出方式主要分为两种:命名导出(Named Exports) 和 默认导出(Default Export)。
1. 命名导出 (Named Exports)
命名导出允许你用指定的名字导出一个或多个变量、函数或类。
方式一:在声明时直接导出
// a.js
export const name = 'Alice';
export let age = 25;
export function sayHello() {
console.log('Hello!');
}
export class User {
constructor(name) {
this.name = name;
}
}
方式二:使用大括号统一导出
这种方式更加清晰,可以一目了然地看到模块导出了哪些内容。
// b.js
const name = 'Bob';
function sayGoodbye() {
console.log('Goodbye!');
}
// 在文件末尾统一导出
export { name, sayGoodbye };
方式三:导出时重命名
如果你想用不同的名字导出,可以使用 as 关键字。
// c.js
function greet() {
console.log('Greetings!');
}
// 将 greet 函数以 hi 的名字导出
export { greet as hi };
2. 默认导出 (Default Export)
每个模块只能有一个默认导出。默认导出时,导入方可以为其指定任意名称。这对于模块只提供单一功能或主要功能的场景非常方便。
// d.js
export default function() {
console.log('This is the default export.');
}
// 或者导出一个类
// export default class User { ... }
// 或者导出一个变量
// const myConfig = { ... };
// export default myConfig;
注意: export default 后面跟的是一个值(表达式),所以不能写成 export default const name = '...';,而应该写成 const name = '...'; export default name;。
3. 混合使用
一个模块可以同时使用命名导出和默认导出。
// e.js
export default function mainFunction() {
// ...
}
export const version = '1.0';
export function helper() {
// ...
}
import:从模块中导入
import 命令用于加载其他模块提供的功能。
1. 导入命名导出的模块
导入命名导出的成员时,必须使用大括号 {},并且变量名必须与导出时的名称一致。
// main.js
import { name, sayHello } from './a.js';
console.log(name); // 'Alice'
sayHello(); // 'Hello!'
导入时重命名
如果导入的变量名与当前作用域的变量冲突,或者你想要一个更具描述性的名字,可以使用 as 关键字。
// main.js
import { name as userName, sayHello as greet } from './a.js';
console.log(userName); // 'Alice'
greet(); // 'Hello!'
2. 导入默认导出的模块
导入默认导出的成员时,不需要使用大括号,并且可以为其指定任意名称。
// main.js
import myCustomFunction from './d.js';
myCustomFunction(); // 'This is the default export.'
3. 导入所有命名导出的成员(通配符 *)
你可以使用星号 (*) 将一个模块中所有命名导出的成员作为一个对象导入。
// main.js
import * as utils from './a.js'; // utils 是你自定义的对象名
console.log(utils.name); // 'Alice'
utils.sayHello(); // 'Hello!'
console.log(utils.age); // 25
注意: import * 不会包含 export default 导出的成员。
4. 混合导入
当一个模块同时有默认导出和命名导出时,可以这样导入:
// main.js
import mainFunc, { version, helper } from './e.js';
mainFunc();
console.log(version); // '1.0'
helper();
mainFunc 是对 e.js 模块默认导出的引用,{ version, helper } 是对命名导出的引用。
5. 仅执行模块(副作用导入)
有时你可能只想执行一个模块中的代码,而不需要导入任何东西(例如,这个模块可能只是向 window 添加了一些 polyfill)。这种情况下,可以直接 import 该文件。
// main.js
import './polyfill.js'; // 仅执行 polyfill.js 文件中的代码
export ... from:模块的转发
这个语法可以将另一个模块的接口聚合到当前模块中,并直接导出,而无需先 import 再 export。
转发命名导出
// aggregator.js
// 将 a.js 中的 name 和 sayHello 导出
export { name, sayHello } from './a.js';
// 转发时重命名
export { hi as greet } from './c.js';
转发所有命名导出
// aggregator.js
export * from './b.js'; // 转发 b.js 的所有命名导出
转发默认导出
转发默认导出需要显式命名。
// aggregator.js
export { default as myDefaultFunction } from './d.js';
注意: export * from './module.js' 不会转发 module.js 的默认导出。
总结与关键点
| 特性 | export (导出) | import (导入) |
| :--- | :--- | :--- |
| 类型 | 命名导出 (export { a }) / 默认导出 (export default b) | 命名导入 (import { a }) / 默认导入 (import b) |
| 数量 | 命名导出可有多个,默认导出只能有一个。 | 可以同时导入命名和默认成员。 |
| 命名 | 命名导出必须使用导出时的名字;默认导出不需要。 | 命名导入必须使用导出时的名字(可用 as 重命名);默认导入可任意命名。 |
| 语法 | export { a } / export default b | import { a } from '...' / import b from '...' |
| 静态性 | 编译时确定接口,只能在顶层作用域使用。 | 编译时确定依赖,只能在顶层作用域使用。 |
| 绑定 | 导出的是值的动态只读引用(Live Binding)。如果模块内部的值改变,import 导入的值也会跟着改变。 | 导入的变量是只读的,不能在当前模块中重新赋值。 |
掌握 import 和 export 是现代 JavaScript 开发的基础。它们不仅让代码更加结构化,也是 Webpack、Rollup 等构建工具进行 Tree Shaking(摇树优化,移除未使用的代码)的技术基础。