exports 是 module.exports 的引用别名,直接赋值 exports 会断开关联导致导出为空;正确方式包括逐个挂载、覆盖 module.exports 或导出单个函数/类,并需避免混用 ES6 与 CommonJS 模块。

exports 是 Node.js 模块系统中用于向外暴露变量、函数或对象的关键机制,它本质是 module.exports 的一个引用别名。实战中若误用(如直接赋值 exports = {...}),会导致模块导出为空,这是最常见也最容易踩的坑。
理解 exports 与 module.exports 的关系
在每个 CommonJS 模块中,Node.js 默认提供两个对象:
-
exports是module.exports的初始引用(浅拷贝指向同一对象) -
module.exports才是真正决定模块对外输出内容的“权威出口” - 一旦对
exports重新赋值(如exports = function() {...}),它就脱离了与module.exports的关联,后续 require 将得不到预期结果
正确暴露核心包变量的三种常用方式
假设你正在开发一个工具包 utils,希望对外暴露 formatDate、deepClone 和常量 MAX_RETRY:
-
方式一:逐个挂载到 exports(推荐初学者)
exports.formatDate = function(date) { ... };<br> exports.deepClone = function(obj) { ... };<br> exports.MAX_RETRY = 3; -
方式二:一次性覆盖 module.exports(更清晰、更常用)
module.exports = {<br> formatDate: function(date) { ... },<br> deepClone: function(obj) { ... },<br> MAX_RETRY: 3<br> }; -
方式三:导出单个构造函数或类(适合封装为实例化对象)
function Utils() {}<br> Utils.prototype.log = function(msg) { console.log('[Utils]', msg) };<br> module.exports = Utils;
实现模块间公共接口访问的典型协作逻辑
以「用户服务」依赖「数据库连接」为例,展示如何通过 exports 建立稳定接口契约:
- 在
db.js中统一暴露连接方法:const pool = createPool(...);<br> exports.query = (sql, params) => pool.execute(sql, params);<br> exports.close = () => pool.end();
- 在
user-service.js中按需导入并使用:const db = require('./db');<br> exports.getUserById = async (id) => {<br> const [rows] = await db.query('SELECT * FROM users WHERE id = ?', [id]);<br> return rows[0];<br> }; - 主应用只需消费最终业务接口:
const userService = require('./user-service');<br> userService.getUserById(123).then(console.log);
避免常见陷阱的实用建议
写错 exports 往往导致 “Cannot destructure property... of undefined” 类错误,排查时重点关注:
- 禁止写
exports = { a: 1 }—— 这会切断引用,应改用module.exports = { a: 1 } - 若同时使用
exports.xxx = ...和module.exports = ...,后者会完全覆盖前者,优先级更高 - ES6 模块(import/export)与 CommonJS(require/exports)不互通;混用时需通过 package.json 的
"type": "module"明确标识,否则可能报错 - 调试技巧:在模块末尾加
console.log('exports:', exports, 'module.exports:', module.exports)快速验证导出内容是否一致

















