ES6模块导入导出方式
ESM 的 import / export 语法与模块边界。
#type / concept
#status / growing
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ecmascript-module-pattern, ecmascript-moc, ES6 新特性 MOC
- 并列概念: commonjs-amd-umd, html-js-scripts
- Node 配置: package-json
- 关系笔记: ecmascript-module-pattern, JavaScript的工程化与运行时
ES6 模块 (ESM)
一句话定义
ESM 是 ECMAScript 原生的模块系统,通过静态的 import / export 语法实现模块间依赖声明,支持静态分析和 Tree Shaking。
核心机制 / 工作原理
- 每个文件是一个模块,拥有独立作用域,模块内的变量不会污染全局。
- 模块只执行一次并缓存结果(单例),多次导入同一模块得到相同引用。
import语句是静态的(编译时确定依赖关系),这使打包工具可以做 Tree Shaking。- ESM 导出的是 活绑定 (live bindings):导入方看到的是导出变量的引用,而非值的拷贝。
命名导出 / 导入
// ---- math.js ----
export const PI = 3.14159;
export function add(a, b) { return a + b; }
// ---- app.js ----
import { PI, add } from './math.js';
import { add as sum } from './math.js'; // 重命名导入
import * as math from './math.js'; // 命名空间导入
默认导出 / 导入
// ---- logger.js ----
export default function log(msg) {
console.log(msg);
}
// ---- app.js ----
import log from './logger.js'; // 名字可以随便取
import myLog from './logger.js';
重新导出 (Re-export)
export { add } from './math.js'; // 转发命名导出
export { default as Logger } from './logger.js'; // 转发默认导出
export * from './math.js'; // 转发所有命名导出(不含 default)
export * as math from './math.js'; // ES2020: 命名空间再导出
动态导入
const module = await import('./heavy-module.js');
module.doSomething();
// 条件导入
if (needsChart) {
const { Chart } = await import('./chart.js');
new Chart(canvas);
}
活绑定 (Live Bindings)
// counter.js
export let count = 0;
export function increment() { count++; }
// app.js
import { count, increment } from './counter.js';
console.log(count); // 0
increment();
console.log(count); // 1 — 活绑定,看到的是最新值
循环依赖行为
ESM 能处理循环依赖,但有一个关键限制:如果模块 A 还没执行完就被 B 导入,B 拿到的是 A 当前已导出的部分(可能是未初始化的值)。
// a.js
import { b } from './b.js';
export const a = 'a';
console.log('b in a:', b); // undefined — b.js 还没执行完
// b.js
import { a } from './a.js';
export const b = 'b';
console.log('a in b:', a); // "a" — a.js 已执行完
ESM vs CJS 快速对比
| 维度 | ESM | CJS |
|---|---|---|
| 语法 | import / export | require() / module.exports |
| 加载时机 | 编译时(静态) | 运行时(动态) |
| 绑定方式 | 活绑定(引用) | 值拷贝 |
| Tree Shaking | 支持 | 不支持 |
| 浏览器原生 | 支持(<script type="module">) | 不支持 |
| 循环依赖 | 部分支持(活绑定) | 可能拿到不完整对象 |
边界与常见误解
import必须在顶层:常规import不能在if或函数内使用(静态分析限制),条件加载请用import()。- 默认导出不是命名导出的语法糖:一个模块可以同时有默认导出和命名导出,它们是不同的导出槽位。
- Node.js 中 ESM 需要配置:要么文件扩展名为
.mjs,要么package.json中设置"type": "module"。