API稳定性取决于模块封装、接口契约和版本控制,而非exports语法;应明确导出边界、冻结导出对象、用命名空间隔离变更,并配合类型校验强化契约。

API 的稳定性不取决于是否用了 exports,而取决于模块封装、接口契约和版本控制机制。exports(或 export)只是 JavaScript 模块系统中暴露内部能力的语法手段,它本身不具备稳定保障能力——暴露什么、如何暴露、何时变更,才真正影响外部依赖。
明确导出边界,只暴露契约接口
避免直接导出实现细节(如工具函数、内部类、未封装的数据结构)。只导出经过抽象的、有明确定义行为的函数或类实例。
- ✅ 推荐:导出一个具名函数
createOrder(),其输入/输出格式、错误类型、幂等性都写在文档里 - ❌ 避免:导出
validateCart()、sendToWarehouse()等中间步骤,外部调用者一旦依赖它们,后续重构就容易断裂
冻结导出对象,防止运行时篡改
对导出的对象使用 Object.freeze() 或 Object.defineProperty() 设置不可写/不可配置属性,防止消费者意外修改其行为或状态。
- 例如:
module.exports = Object.freeze({ createOrder, cancelOrder }) - 尤其适用于导出单例服务对象,避免下游通过
api.cancelOrder = null破坏一致性
用命名空间或版本前缀隔离变更
不直接替换 exports,而是按语义分组导出,例如 v1 和 v2 并存,或用 stable / experimental 标记:
module.exports = { v1: require('./api/v1'), v2: require('./api/v2') };- 这样外部可通过
const { v1 } = require('my-api')显式锁定版本,升级时需主动切换,不会静默破坏
配合类型定义与校验强化契约
仅靠 exports 无法阻止传入非法参数。应在导出函数入口做轻量校验(如 Zod、Joi 或 TypeScript 运行时断言),并让错误信息明确指向契约要求。
- 例如:
createOrder在执行前检查order.items是否为数组、order.id是否符合 UUID 格式 - 失败时抛出带
code: 'INVALID_INPUT'和details字段的错误,便于外部统一处理

















