Node.js 中 module.exports 与 exports 有什么区别
module.exports 是模块真正导出的对象,exports 只是它的引用别名。给 exports 添加属性等价于修改 module.exports,但直接给 exports 重新赋值会断开引用导致导出失效。导出函数或对象请直接赋值 module.exports。
module.exports 是 require() 真正拿到的对象;exports 只是指向它的快捷引用。 给 exports 挂属性(exports.foo = ...)等价于 module.exports.foo = ...;但直接整体赋值 exports = function(){} 会断开两者的引用关系,导出不会生效——整体导出必须用 module.exports = ...。
两种正确用法
// 方式一:exports 挂属性(导出多个成员)
exports.getUser = function() { /* ... */ };
exports.createUser = function() { /* ... */ };
// 方式二:module.exports 整体赋值(导出函数/类/对象)
module.exports = function() {
console.log('Hello');
};
常见错误
// 错误!exports 被重新赋值后不再指向 module.exports
exports = function() { console.log('不会被导出'); };
require 这个模块得到的仍是空对象 {},因为模块系统返回的是 module.exports,而你只改了局部变量 exports 的指向。
原理一句话
每个模块加载时 Node 大致执行了 var exports = module.exports = {};,最后 return module.exports。所以:
- 改属性 → 两者都看到(同一对象)✅
- 整体替换 → 只有
module.exports =有效 ✅ exports =整体替换 → 断开引用,无效 ❌
常见问题(FAQ)
Q:两种写法能混用吗?
不建议。如果最终执行了 module.exports = {...},之前通过 exports 挂的属性全部作废(旧对象被丢弃)。一个模块统一用一种风格。
Q:ES Module 里有这个问题吗?
没有。ESM 用 export / export default,语法层面就是静态导出声明,不存在引用陷阱。新代码建议直接用 ESM。
Q:怎么导出既包含函数又包含属性?
给函数对象挂属性:module.exports = main; module.exports.helper = helper;。