export default 与具名导出在 ES Module 中的行为机制截然不同,核心差异不在于“值如何传递”,而在于绑定如何建立以及导入时如何使用。先给出总结性结论,再逐一详细拆解。

export default 是一种语法糖,而非真正的变量声明
这种设计容易引起误解。实际上,export default 并不会创建一个名为 default 的变量,而是为模块设置一个特殊的默认导出入口。举例说明:
export default function greet() { return 'hi'; }
上述代码实际上等价于:引擎内部生成一个隐式绑定(例如 __default__),并将这个绑定标记为 default 导出项。关键点在于,该函数并未绑定到当前作用域——它仅仅是作为模块的默认导出接口存在。
因此,以下写法属于语法错误:
export default const x = 1; // 语法错误
export default let y = 2; // 同样非法
允许的只有导出声明(函数、类)或导出表达式(对象字面量、箭头函数等)。换言之,export default 后面跟的并非变量声明,而是一个值或一个可挂名的声明形式。
具名导出绑定标识符,且必须提前声明
与 export default 不同,所有具名导出都要求引用模块顶层已存在的声明。以下示例均为合法写法:
const a = 1; export { a };function foo() {} export { foo };class Bar {} export { Bar };
这里有一个关键区别:具名导出建立起静态、只读且实时的绑定。所谓“实时”,指的是如果修改了 a 的值,所有 import { a } 的地方都会自动获取新值。但导入方只能读取,不能重新赋值——尝试给 a 赋值会直接报错(Assignment to const)。这正是 ES Module 在模块边界上实现安全引用传递的核心机制。
导入方式截然不同,影响可读性与工具链效率
具名导出要求导入方严格使用相同的名称:
import { foo, Bar } from './mod.js';
而 default 导出则完全自由,导入时可以任意命名:
import greet from './mod.js';
import somethingElse from './mod.js';
这种灵活性看似方便,但会带来三个实际痛点:
- 重构困难:修改函数名后,所有 default 导入点不会报错,但逻辑可能断裂
- 类型推导较弱:TypeScript 和编辑器对 default 导出的内容难以准确补全
- Tree-shaking 效果不佳:打包工具更难判断 default 导出是否真正被使用
正因如此,许多资深开发者建议优先采用具名导出,尤其是在大型项目或库开发中,具名导出的可维护性明显更优。
两者不能重复导出同一标识符
一个模块可以同时拥有 default 导出和具名导出,但不能重复导出同一个标识符。例如:
const utils = { log() {} };
export default utils;
export { utils }; // ✅ 合法,导出的是不同绑定
上述写法没有问题。但以下写法会导致报错:
export const x = 1;
export default x; // ❌ 不能把已具名导出的 x 再作为 default 导出
原因很简单:default 导出要求使用独立的表达式或声明,而非对已有标识符的别名引用。这是设计上刻意施加的约束,旨在避免绑定关系产生歧义。
将这两种出口机制理清后,ES Module 的动态行为就清晰了大半。归根结底,模块系统内部的绑定机制才是理解一切的起点——导出语法只是表象。
