关键在于只导出契约不导出实现:API模块仅声明稳定接口,实现模块封装私有逻辑并用默认导出隔离,辅以下划线命名和作用域控制隐藏内部变量。
要真正实现 api 模块与实现模块的分离,并隐藏内部变量,关键不是“多导出”,而是“只导出契约,不导出实现”。exports(或 export)本身只是门把手,真正的隔离靠的是模块边界设计和访问控制意识。
明确划分接口层与实现层
把对外承诺的功能定义在独立的 API 模块中,所有函数签名、类型、常量都只在这里声明;而具体逻辑、中间状态、辅助工具全放在实现模块里,绝不暴露给外部。
- API 模块(如
user.api.js)只含export function createUser(...)、export const USER_ROLES = {...}等稳定契约 - 实现模块(如
user.impl.js)包含function _validateEmail() {...}、let _nextId = 1、class UserDB {...}等私有内容,不使用export声明任何成员 - API 模块通过
import { ... } from './user.impl.js'调用实现,但外部永远看不到./user.impl.js的路径或内容
用默认导出封装完整能力,切断内部成员访问链
如果实现模块需要被 API 模块调用,又不想让它的任何成员意外泄露,就用 export default 导出一个封闭对象,而不是用命名导出暴露一堆零散函数。
- 在
user.impl.js中写:export default { create: () => {...}, delete: () => {...} } - 在
user.api.js中写:import impl from './user.impl.js'; export const createUser = impl.create; - 这样外部导入
user.api.js后,只能拿到createUser,完全无法访问impl对象本身,更看不到其内部结构
借助作用域与命名约定主动隐藏变量
JavaScript 没有真正的私有关键字(ES2022 的 # 字段除外),所以必须靠机制+习惯双重防护:
- 所有内部变量、辅助函数一律用下划线前缀,如
const _cache = new Map()、function _normalizeName() {...} - 避免在模块顶层声明可变全局状态;若需共享状态,用 IIFE 或类实例封装,例如:
const _storage = (() => { let data = []; return { get: () => data, add: (x) => data.push(x) }; })(); - 绝不把实现模块直接
export * from './xxx.impl'—— 这等于敞开大门
Node.js 环境下慎用 exports 与 module.exports 混用
CommonJS 中,exports 只是 module.exports 的快捷引用。一旦你执行了 module.exports = xxx,再往 exports 上挂属性就无效了——这容易造成“以为导出了,其实没导出”的陷阱。
- 统一风格:要么全用
exports.xxx = ...导出多个项,要么直接module.exports = { a, b, c }或module.exports = class X - API 模块建议始终用
module.exports = { ... }显式返回一个干净对象,避免歧义 - 检查是否导出成功:在终端运行
node -e "console.log(require('./user.api'))",确认输出只有预期字段
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










