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

相关推荐
老前端的功夫38 分钟前
前端浏览器缓存深度解析:从网络请求到极致性能优化
前端·javascript·网络·缓存·性能优化
颜酱2 小时前
Monorepo 架构以及工具选型、搭建
前端·javascript·node.js
X***48963 小时前
JavaScript在Node.js中的Nx
javascript·node.js·vim
o***Z4483 小时前
JavaScript在Node.js中的内存管理
开发语言·javascript·node.js
我命由我123453 小时前
微信开发者工具 - 模拟器分离窗口与关闭分离窗口
前端·javascript·学习·微信小程序·前端框架·html·js
S***42803 小时前
JavaScript在Web中的Angular
前端·javascript·angular.js
4***14903 小时前
Vue代码规范详解
javascript·vue.js·代码规范
San304 小时前
深入理解 JavaScript 词法作用域链:从代码到底层实现机制
前端·javascript·ecmascript 6
进击的野人4 小时前
深入理解 JavaScript Promise:原理、用法与实践
javascript·面试·ecmascript 6
我有一棵树4 小时前
file 协议与 http 协议的区别:为什么本地 HTML 无法加载相对路径 JS,以及正确的解决方式
javascript·http·html