Node.js 里的 exports
和 module.exports
,到底怎么回事?
经常学(手动滑稽) Node.js 的人一定遇到过这种困惑:
exports.hello = hello
可以正常导出。- 但直接写
exports = hello
却没效果。 - 到底为什么?Node.js 内部是怎么处理的?
别急,这事儿其实很简单,咱来捋一捋。
1. 模块初始化的时候,发生了什么?
当 Node.js 加载一个模块时,底层大致做了这样的事:
javascript
const module = {
exports: {}
};
const exports = module.exports;
简单理解:
module.exports
是真正用来导出内容的那个对象。exports
只是一个引用 ,最开始指向module.exports
。
也就是说,一开始这俩是绑在一起的,一个动俩动。
2. 三种导出写法,到底差在哪?
在模块内部,常见的三种写法,其实背后的内存变化不一样。
写法一:module.exports = hello
javascript
module.exports = hello;
这时候,直接把原来那个导出对象整个换掉 ,指向了新的 hello
函数。
module.exports
→ helloexports
→ 还是指向之前那个{}
,但已经没用了
✅ 有效 :因为 Node 最后返回的是 module.exports
,换掉就生效了。
写法二:exports.hello = hello
javascript
exports.hello = hello;
// 等价于 module.exports.hello = hello
这时候是给对象添加一个属性,没换对象本身。
module.exports
→{ hello: hello函数 }
exports
→ 仍然指向这个对象
✅ 有效:因为两个指针还是指着同一块内存,改一个就是改俩。
写法三:exports = hello
javascript
exports = hello; // ❌
注意了,这个就有坑了:
exports
被重新赋值,变成了指向hello
- 但
module.exports
还是原来的{}
,一点没动
❌ 无效:
exports
本质上是个局部变量- 你给它重新赋值,只是改了自己,并不会影响
module.exports
- Node.js 最后只看
module.exports
,跟exports
变化没半毛钱关系
说白了,就是一开始给你个笔,和纸连着,你写字当然能写到纸上。
但你自己把笔换了,纸还在那,Node.js 只认那张纸。
3. 打个比方,一秒理解
如果把 module.exports
和 exports
的关系打个比方:
module.exports
就是真正的快递盒子。exports
是给你准备的临时小标签,方便你往盒子里贴东西。
那三种操作的区别呢?
做法 | 结果 |
---|---|
module.exports = 新内容 |
✅ 换了整个快递盒,收的人能收到新盒子 |
exports.xxx = 内容 |
✅ 往原来那个盒子里塞了新东西 |
exports = 新内容 |
❌ 换了个新标签,快递盒还在原地,没人看你新换的 |
所以说,不管你怎么折腾,Node.js 最后只认那个原本的快递盒 ------也就是 module.exports
。
4. Node.js 到底是怎么执行模块的?
其实 Node.js 加载你的模块时,内部干了这么一件事(伪代码):
javascript
function require(modulePath) {
// 1. 先准备好 module 和 exports
const module = { exports: {} };
const exports = module.exports;
// 2. 执行你的模块代码
(function (exports, module) {
// 你的模块内容在这里跑
exports = hello; // ❌ 只是换了exports,module.exports没动
module.exports = hello; // ✅ 正确,把真正要导出的东西挂到module.exports上
})(exports, module);
// 3. 最后返回 module.exports
return module.exports;
}
注意看重点:
exports
只是入口时传进去的一个变量。- 最终
require()
拿回来的永远是module.exports
。 - 你中途怎么改
exports
,只要没动module.exports
,都白搭。
简单理解就是:
Node.js 开模块的时候,给你一份空快递盒子(module.exports = {}
),让你随便往里面塞东西。
结果你半路换了支笔(exports = xxx
),盒子里的东西还是空的,最后送出去的自然是空盒子。
5. 终极总结
exports
只是个局部变量 ,是为了方便你往module.exports
里添加属性。- 真正要导出的内容 ,始终是
module.exports
。 - 不要用
exports = xxx
,那等于是在重新给局部变量赋值,根本不会影响到module.exports
,也就无法被外部require()
到。
也就是说:
- 如果你要改变整个导出内容 ,记得直接操作
module.exports
。 - 如果你要给模块添加属性 ,直接操作
exports
就行。