Node.js 模块加载与 exports 和 module.exports 的区别

Node.js 里的 exportsmodule.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 → hello
  • exports → 还是指向之前那个 {},但已经没用了

有效 :因为 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.exportsexports 的关系打个比方:

  • 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 就行。

相关推荐
会飞的鱼先生8 分钟前
vue3 内置组件KeepAlive的使用
前端·javascript·vue.js
前端大白话1 小时前
前端崩溃瞬间救星!10 个 JavaScript 实战技巧大揭秘
前端·javascript
一千柯橘1 小时前
Nestjs 解决 request entity too large
javascript·后端
举个栗子dhy2 小时前
如何处理动态地址栏参数,以及Object.entries() 、Object.fromEntries()和URLSearchParams.entries()使用
javascript
宁静_致远2 小时前
React Native 技术栈:基于 macOS 开发平台的 iOS 应用开发指南
前端·javascript·react native
H5开发新纪元2 小时前
VS Code 插件开发实战:代码截图工具
javascript·visual studio code
DevUI团队2 小时前
超越input!基于contentediable实现github全局搜索组件:从光标定位到输入事件的全链路设计
前端·javascript
天天扭码2 小时前
前端必备技能 | 使用rem实现移动页面响应式
前端·javascript·css
Momoyouta2 小时前
draggable拖拽列表与虚拟列表结合实例
前端·javascript
magic 2452 小时前
深入解析Promise:从基础原理到async/await实战
开发语言·前端·javascript