exports 是 Node.js CommonJS 中预定义的对象,用于显式导出模块接口;真正封装靠模块作用域封闭性及有意识地最小化暴露内容,而非语法强制。

exports 不是“指令”,而是 Node.js CommonJS 模块系统中一个预定义的对象,用于显式声明模块对外暴露的接口。它本身不定义边界、也不强制封装——真正起作用的是模块作用域天然的封闭性,以及你是否只通过 exports(或 module.exports)有意识地暴露最小必要内容。
明确模块边界:只导出真正需要被外部使用的成员
Node.js 每个文件默认是一个独立作用域,内部变量、函数、类默认对外不可见。边界是否清晰,取决于你有没有“主动收口”:
- 不把内部工具函数、临时变量、配置常量直接挂到
exports上 - 避免使用
exports = { ... }全量覆盖(这会断开与module.exports的引用,导致导入失败) - 优先用
module.exports = { ... }替代多次赋值,逻辑更集中、意图更明确
用 exports 实现变量封装:靠“不导出”比靠“导出”更关键
封装不是靠语法强制,而是靠设计约束。CommonJS 中没有 private 关键字,但你可以通过以下方式守住封装规则:
- 把敏感数据或中间状态定义在模块顶层作用域,但绝不挂到
exports上 - 如果必须提供读写控制,导出的是方法(如
getCounter()/resetCounter()),而不是原始变量 - 导出对象时避免返回可变引用,例如不要直接
exports.config = internalConfig,而应返回克隆或只读代理
常见误用与修正示例
下面这些写法看似简洁,实则破坏封装或导致行为异常:
-
错误:
exports = { init: () => {...} }→ 导致其他文件require('./x')得到空对象 -
危险:
exports.dbConnection = internalDbConn→ 外部可随意修改连接状态 -
推荐:
module.exports = { start(), stop(), getStatus() }→ 接口收敛、状态隔离
配合 require 建立可验证的依赖契约
模块边界最终体现在调用方如何使用。清晰的 exports 设计能让 require 调用一目了然:
- 调用方只看到你愿意提供的方法名和结构,无法访问内部实现细节
- 一旦你删掉某个
exports.xxx,所有依赖它的代码会在运行时报错(undefined is not a function),这就是边界被违反时的自然反馈 - 结合 JSDoc 或 TypeScript 类型标注,还能让这种契约在编辑器中实时可见










