ChatGPT解决这个技术问题 Extra ChatGPT

Node.js 中的 module.exports 与导出

我在 Node.js 模块中找到了以下合同:

module.exports = exports = nano = function database_module(cfg) {...}

我想知道 module.exportsexports 之间有什么区别以及为什么在这里使用两者。

这都是关于参考的。将导出视为指向 module.exports 的局部变量对象。如果您覆盖了exports 的值,那么您将失去对module.exports 的引用,而module.exports 就是您作为公共接口公开的内容。
快速总结: exportsmodule.exports 都指向同一个对象,除非您重新分配一个。最后返回 module.exports。因此,如果您将 exports 重新分配给一个函数,那么不要指望一个函数,因为它不会被返回。但是,如果您分配了像 exports.func = function... 这样的函数,那么生成的东西将具有 func 属性,函数作为值。因为您将属性添加到 exports 指向的对象..

r
riQQ

尽管很久以前就已经回答并接受了问题,但我只想分享我的 2 美分:

您可以想象在文件的开头有类似的内容(仅用于解释):

var module = new Module(...);
var exports = module.exports;

https://i.stack.imgur.com/JzZkz.png

因此,无论您做什么,请记住,当您从其他地方需要该模块时,将从您的模块返回 module.exports 而不是 exports

因此,当您执行以下操作时:

exports.a = function() {
    console.log("a");
}
exports.b = function() {
    console.log("b");
}

您正在向 module.exports 指向的对象添加 2 个函数 ab,因此 typeof 返回结果将是 object{ a: [Function], b: [Function] }

当然,如果您在此示例中使用 module.exports 而不是 exports,您将获得相同的结果。

在这种情况下,您希望 module.exports 的行为类似于导出值的容器。然而,如果您只想导出构造函数,那么您应该了解使用 module.exportsexports 的一些知识;(再次记住,当您需要某些东西时将返回 module.exports,而不是 export)。

module.exports = function Something() {
    console.log('bla bla');
}

现在 typeof 返回结果是 'function',您可以要求它并立即调用:
var x = require('./file1.js')(); 因为您将返回结果覆盖为一个函数。

但是,使用 exports 您不能使用以下内容:

exports = function Something() {
    console.log('bla bla');
}
var x = require('./file1.js')(); //Error: require is not a function

因为使用 exports,引用 不再指向 module.exports 指向的对象,因此 exportsmodule.exports 之间不再存在关系。在这种情况下,module.exports 仍然指向将返回的空对象 {}

另一个主题的接受答案也应该有帮助:Does JavaScript pass by reference?


很好的解释,但我仍然不明白如何从模块中完全省略 module.exports,例如在这个 npm 包中:github.com/tj/consolidate.js/blob/master/lib/consolidate.js
@Imray 解释在这里:Does JavaScript pass by reference? exports.a = function(){}; works, exports = function(){} doesn't work
oooo 最后这个答案解释了它。基本上导出是指您可以添加属性的对象,但如果您将其重新分配给功能,那么您不再将属性附加到该原始对象。现在 export 引用函数,而 module.exports 仍然指向该对象,因为它是返回的内容。你可以说出口基本上已经被垃圾收集了。
那么,使用 exports 有什么意义呢?如果它只是一个变量重新分配,为什么不总是使用 module.exports 呢?对我来说似乎很困惑。
@jedd.ahyoung 通过向 exports 添加属性,您可以有效地确保返回“典型”模块导出 object。相反,通过使用 module.exports,您可以返回任何您想要的值(原始、数组、函数),而不仅仅是一个对象(这是大多数人期望的格式)。因此 module.exports 提供了更多功能,但也可用于让您的模块导出非典型值(如原语)。相比之下,exports 限制更多但更安全(只要您简单地向它添加属性并且不重新分配它)。
C
Community

设置 module.exports 允许在 required 时像函数一样调用 database_module 函数。简单地设置 exports 将不允许导出函数,因为节点导出对象 module.exports 引用。以下代码不允许用户调用该函数。

模块.js

以下将不起作用。

exports = nano = function database_module(cfg) {return;}

如果设置了 module.exports,以下将起作用。

module.exports = exports = nano = function database_module(cfg) {return;}

安慰

var func = require('./module.js');
// the following line will **work** with module.exports
func();

基本上 node.js 不会导出 exports 当前引用的对象,而是导出 exports 最初引用的对象的属性。虽然 Node.js 确实导出了对象 module.exports 引用,但您可以像调用函数一样调用它。

第二个最不重要的原因

他们同时设置 module.exportsexports 以确保 exports 没有引用先前导出的对象。通过设置两者,您可以使用 exports 作为速记,并避免以后出现潜在的错误。

使用 exports.prop = true 而不是 module.exports.prop = true 可以节省字符并避免混淆。


@ajostergaard:它恰好是 OP 示例的 library 的名称。在模块中,它允许作者写像 nano.version = '3.3' 而不是 module.exports.version = '3.3' 之类的东西,这样读起来更清楚一些。 (注意 nano 是一个局部变量 declared a little before the module exports are set。)
@lime - 谢谢 - 我很高兴这在很大程度上无关紧要,因为如果不是,那将意味着我完全误解了一切。 :-| :)
嘿石灰,这是一个很老的答案,但我希望你能澄清一些事情。如果我设置 module.exports exports,我的代码还能工作吗?谢谢你的帮助!
@Asad 是的,如果您设置 module.exports,该函数将正确导出
我不知道他们为什么不为 exports 申请 set Proxy
c
caesarsol

基本上,答案在于通过 require 语句需要模块时真正发生的情况。假设这是第一次需要该模块。

例如:

var x = require('file1.js');

file1.js 的内容:

module.exports = '123';

执行上述语句时,会创建一个 Module 对象。它的构造函数是:

function Module(id, parent) {
    this.id = id;
    this.exports = {};
    this.parent = parent;
    if (parent && parent.children) {
        parent.children.push(this);
    }

    this.filename = null;
    this.loaded = false;
    this.children = [];
}

如您所见,每个模块对象都有一个名为 exports 的属性。这是最终作为 require 的一部分返回的内容。

require 的下一步是将 file1.js 的内容包装到一个匿名函数中,如下所示:

(function (exports, require, module, __filename, __dirname) { 
    //contents from file1.js
    module.exports = '123;
});

而这个匿名函数的调用方式如下,这里的module指的是前面创建的Module对象。

(function (exports, require, module, __filename, __dirname) { 
    //contents from file1.js
    module.exports = '123;
}) (module.exports,require, module, "path_to_file1.js","directory of the file1.js");

正如我们在函数内部看到的,exports 形式参数指的是 module.exports。本质上,这是为模块程序员提供的便利。

然而,这种便利需要谨慎使用。在任何情况下,如果尝试将新对象分配给导出,请确保我们这样做。

exports = module.exports = {};

如果我们按照错误的方式进行操作,module.exports 仍将指向作为模块实例的一部分创建的对象。

exports = {};

因此,向上述导出对象添加任何内容都不会影响 module.exports 对象,并且不会作为 require 的一部分导出或返回任何内容。


我在这里迷路了exports = module.exports = {};
我认为这应该是最好的答案,它解释了为什么 func() 在@William 的答案中失败了!
我看不出在代码的最后一行添加 exports = module.exports = app; 有什么好处。似乎 module.exports 将被导出,我们将永远不会使用 exports,因为它再次位于代码的最后一行。那么,我们为什么不简单地添加 module.exports = app;
m
mic4ael

最初,module.exports=exportsrequire 函数返回 module.exports 引用的对象。

如果我们向对象添加属性,例如 exports.a=1,那么 module.exports 和 export 仍然 引用同一个对象。所以如果我们调用require并将模块赋值给一个变量,那么这个变量有一个属性a,它的值为1;

但是如果我们覆盖其中一个,例如 exports=function(){},那么它们现在就不同了:exports 指的是一个新对象,而 module.exports 指的是原始对象.如果我们需要文件,它不会返回新对象,因为 module.exports 不是引用新对象。

对我来说,我将继续添加新属性,或者将它们都覆盖到一个新对象中。只覆盖一个是不对的。请记住,module.exports 才是真正的老板。


是的,这才是真正的答案。它简洁明了。其他人可能是对的,但充满了花哨的术语,并没有完全专注于这个问题的答案。
d
dustin.schultz

exportsmodule.exports 是相同的,除非您在模块中重新分配 exports

考虑它的最简单方法是认为这条线隐含在每个模块的顶部。

var exports = module.exports = {};

如果在您的模块中重新分配 exports,那么您在模块中重新分配它并且它不再等于 module.exports。这就是为什么,如果你想导出一个函数,你必须这样做:

module.exports = function() { ... }

如果您只是将 function() { ... } 分配给 exports,您将重新分配 exports 以不再指向 module.exports

如果您不想每次都通过 module.exports 引用您的函数,您可以执行以下操作:

module.exports = exports = function() { ... }

请注意,module.exports 是最左边的参数。

将属性附加到 exports 是不同的,因为您没有重新分配它。这就是为什么这有效

exports.foo = function() { ... }

J
Jakob Larsen

JavaScript 通过引用的副本传递对象

这与 JavaScript 中对象通过引用传递的方式存在细微差别。

exportsmodule.exports 都指向同一个对象。 exports 是一个变量,module.exports 是模块对象的一个属性。

假设我写了这样的东西:

exports = {a:1};
module.exports = {b:12};

exportsmodule.exports 现在指向不同的对象。修改导出不再修改 module.exports。

当导入函数检查 module.exports 时,它会得到 {b:12}


“JavaScript 通过引用传递” – 否。
我读到这里的最佳答案。但是有一个问题:如果 const b = require() 给我 {b:12},我如何导入它来获得 {a:1}
L
Lyman Lai

我只是做了一些测试,结果发现,在 nodejs 的模块代码中,它应该是这样的:

var module.exports = {};
var exports = module.exports;

所以:

1:

exports = function(){}; // this will not work! as it make the exports to some other pointer
module.exports = function(){}; // it works! cause finally nodejs make the module.exports to export.

2:

exports.abc = function(){}; // works!
exports.efg = function(){}; // works!

3:但是 在这种情况下

module.exports = function(){}; // from now on we have to using module.exports to attach more stuff to exports.
module.exports.a = 'value a'; // works
exports.b = 'value b'; // the b will nerver be seen cause of the first line of code we have do it before (or later)

莱曼,所以 module.exports 是那种节点关闭的“真正的交易”,但在某些时候,您需要将所有 exports 添加到 module.exports,除非您使用 exports.namespace(案例2),在这种情况下,这似乎是 Node 运行了一个 extends(module.exports, exports);,将 exports 的所有“命名空间”添加到 module.exports 对象?换句话说,如果您使用的是 exports,那么您可能想要在其上设置属性?
S
Salar

这是 Manning 出版物中关于 node.js 中节点模块的一个很好的描述。最终在您的应用程序中导出的是 module.exports。 export 被简单地设置为对 module.exports 的全局引用,它最初被定义为一个可以添加属性的空对象。所以exports.myFunc 只是module.exports.myFunc 的简写。因此,如果 export 设置为其他任何值,它会破坏 module.exports 和 exports 之间的引用。因为 module.exports 是真正被导出的,所以 export 将不再按预期工作——它不再引用 module .exports。如果要维护该链接,可以再次使 module.exports 引用导出,如下所示:

module.exports = exports = db;

m
magentaqin

要了解这些差异,您必须首先了解 Node.js 在运行时对每个模块的作用。 Node.js 为每个模块创建一个包装函数:

 (function(exports, require, module, __filename, __dirname) {

 })()

请注意,第一个参数 exports 是一个空对象,第三个参数 module 是一个具有许多属性的对象,其中一个属性名为 exports。这就是 exports 的来源和 module.exports 的来源。前者是变量对象,后者是module对象的属性。

在模块中,Node.js 会在开头自动执行此操作:module.exports = exports最终返回 module.exports

因此您可以看到,如果您将某个值重新分配给 exports,它不会对 module.exports 产生任何影响。 (仅仅是因为 exports 指向另一个新对象,但 module.exports 仍然持有旧的 exports

let exports = {};
const module = {};
module.exports = exports;

exports = { a: 1 }
console.log(module.exports) // {}

但是如果您更新 exports 的属性,它肯定会对 module.exports 产生影响。因为它们都指向同一个对象。

let exports = {};
const module = {};
module.exports = exports;

exports.a = 1;
module.exports.b = 2;
console.log(module.exports) // { a: 1, b: 2 }

另请注意,如果您将另一个值重新分配给 module.exports,那么 exports 更新似乎没有意义。 exports 上的每次更新都会被忽略,因为 module.exports 指向另一个对象。

let exports = {};
const module = {};
module.exports = exports;

exports.a = 1;
module.exports = {
  hello: () => console.log('hello')
}
console.log(module.exports) // { hello: () => console.log('hello')}

C
Cody

我经历了一些测试,我认为这可能会对这个主题有所启发......

app.js

var ...
  , routes = require('./routes')
  ...;
...
console.log('@routes', routes);
...

/routes/index.js 的版本:

exports = function fn(){}; // outputs "@routes {}"

exports.fn = function fn(){};  // outputs "@routes { fn: [Function: fn] }"

module.exports = function fn(){};  // outputs "@routes function fn(){}"

module.exports.fn = function fn(){};  // outputs "@routes { fn: [Function: fn] }"

我什至添加了新文件:

./routes/index.js

module.exports = require('./not-index.js');
module.exports = require('./user.js');

./routes/not-index.js

exports = function fn(){};

./routes/user.js

exports = function user(){};

我们得到输出“@routes {}”

./routes/index.js

module.exports.fn = require('./not-index.js');
module.exports.user = require('./user.js');

./routes/not-index.js

exports = function fn(){};

./routes/user.js

exports = function user(){};

我们得到输出“@routes { fn: {}, user: {} }”

./routes/index.js

module.exports.fn = require('./not-index.js');
module.exports.user = require('./user.js');

./routes/not-index.js

exports.fn = function fn(){};

./routes/user.js

exports.user = function user(){};

我们得到输出“@routes { user: [Function: user] }” 如果我们将 user.js 更改为 { ThisLoadedLast: [Function: ThisLoadedLast] },我们将得到输出“@routes { ThisLoadedLast: [Function: ThisLoadedLast] }”。

但是如果我们修改 ./routes/index.js...

./routes/index.js

module.exports.fn = require('./not-index.js');
module.exports.ThisLoadedLast = require('./user.js');

./routes/not-index.js

exports.fn = function fn(){};

./routes/user.js

exports.ThisLoadedLast = function ThisLoadedLast(){};

...我们得到“@routes { fn: { fn: [Function: fn] }, ThisLoadedLast: { ThisLoadedLast: [Function: ThisLoadedLast] } }”

所以我建议总是在你的模块定义中使用 module.exports

我不完全理解 Node 内部发生了什么,但如果你能更清楚地理解这一点,请发表评论,因为我相信它会有所帮助。

-- 快乐编码


我认为它们是不必要的复杂和混乱。它应该是透明和直观的。
我同意。在某些情况下,它可能对命名空间有用,但通常不会造成或破坏任何东西。
o
onmyway133

这显示了 require() 如何以最简单的形式工作,摘自 Eloquent JavaScript

问题 模块不能直接导出导出对象以外的值,例如函数。例如,一个模块可能只想导出它定义的对象类型的构造函数。现在,它不能这样做,因为 require 总是使用它创建的 exports 对象作为导出值。

解决方案 为模块提供另一个变量 module,它是一个具有属性 exports 的对象。此属性最初指向由 require 创建的空对象,但可以用另一个值覆盖以导出其他内容。

function require(name) {
  if (name in require.cache)
    return require.cache[name];
  var code = new Function("exports, module", readFile(name));
  var exports = {}, module = {exports: exports};
  code(exports, module);
  require.cache[name] = module.exports;
  return module.exports;
}
require.cache = Object.create(null);

我不得不在 Node 中重新创建它并测试一些东西,直到我得到,我糟透了。基本上,为模块创建的内部函数甚至从不返回导出对象。所以“exports”对象实际上并没有在模块中重新分配,例如,如果您尝试直接编写exports =“this now is a string”。该对象仅作为参考存在。这是我认为直到现在我还没有真正理解的行为。
s
serkan

这是结果

console.log("module:");
console.log(module);

console.log("exports:");
console.log(exports);

console.log("module.exports:");
console.log(module.exports);

https://i.stack.imgur.com/0cy3Q.png

还:

if(module.exports === exports){
    console.log("YES");
}else{
    console.log("NO");
}

//YES

注意:CommonJS 规范只允许使用 exports 变量来公开公共成员。因此,命名导出模式是唯一真正与 CommonJS 规范兼容的模式。 module.exports 的使用是 Node.js 提供的扩展,用于支持更广泛的模块定义模式。


A
Anson Hwang
var a = {},md={};

//首先,exports和module.exports指向同一个空Object

exp = a;//exports =a;
md.exp = a;//module.exports = a;

exp.attr = "change";

console.log(md.exp);//{attr:"change"}

//如果你将 exp 指向其他对象而不是指向它的属性到其他对象。 md.exp 将为空 Object {}

var a ={},md={};
exp =a;
md.exp =a;

exp = function(){ console.log('Do nothing...'); };

console.log(md.exp); //{}

C
Community

docs

export 变量在模块的文件级范围内可用,并在评估模块之前分配了 module.exports 的值。它允许使用快捷方式,因此 module.exports.f = ... 可以更简洁地写为 export.f = ...。但是,请注意,与任何变量一样,如果为 export 分配了一个新值,则它是不再绑定到 module.exports:

它只是一个指向 module.exports 的变量。


P
Paweł Gościcki

我发现此链接对于回答上述问题很有用。

http://timnew.me/blog/2012/04/20/exports-vs-module-exports-in-node-js/

添加到其他帖子节点中的模块系统确实

var exports = module.exports 

在执行您的代码之前。所以当你想 export = foo 时,你可能想做 module.exports = exports = foo 但使用 exports.foo = foo 应该没问题


m
madKakoo

“如果您希望模块导出的根是一个函数(例如构造函数),或者如果您想在一个分配中导出一个完整的对象而不是一次构建一个属性,请将其分配给 module.exports 而不是出口。” - http://nodejs.org/api/modules.html


D
Dmitry Sergeev

让我们用 2 种方式创建一个模块:

单程

var aa = {
    a: () => {return 'a'},
    b: () => {return 'b'}
}

module.exports = aa;

第二种方式

exports.a = () => {return 'a';}
exports.b = () => {return 'b';}

这就是 require() 集成模块的方式。

第一种方式:

function require(){
    module.exports = {};
    var exports = module.exports;

    var aa = {
        a: () => {return 'a'},
        b: () => {return 'b'}
    }
    module.exports = aa;

    return module.exports;
}

第二种方式

function require(){
    module.exports = {};
    var exports = module.exports;

    exports.a = () => {return 'a';}
    exports.b = () => {return 'b';}

    return module.exports;
}

J
Justin Pathrose Vareed

module.exportsexports 在评估模块之前都指向同一个对象。

当您的模块在另一个模块中使用 require 语句时,您添加到 module.exports 对象的任何属性都将可用。 exports 是可用于同一事物的快捷方式。例如:

module.exports.add = (a, b) => a+b

相当于写:

exports.add = (a, b) => a+b

因此,只要您不为 exports 变量分配新值就可以了。当你做这样的事情时:

exports = (a, b) => a+b 

当您为 exports 分配一个新值时,它不再引用导出的对象,因此将保持在您的模块的本地。

如果您打算为 module.exports 分配一个新值,而不是向可用的初始对象添加新属性,您可能应该考虑按以下方式进行操作:

module.exports = exports = (a, b) => a+b

Node.js website has a very good explanation of this.


M
Matt

1.exports -> 用作单例实用程序 2. module-exports -> 用作逻辑对象,例如服务、模型等


C
Community

为什么两者都在这里使用

我相信他们只是想清楚 module.exportsexportsnano 指向同一个函数 - 允许您使用任一变量来调用文件中的函数。 nano 为函数的作用提供了一些上下文。

exports 不会被导出(只有 module.exports 会),那么为什么还要覆盖它呢?

冗长权衡限制了未来出现错误的风险,例如在文件中使用 exports 而不是 module.exports。它还澄清 module.exportsexports 实际上指向相同的值。

module.exports 与导出

只要您不重新分配 module.exportsexports(而是将值添加到它们都引用的对象),您就不会有任何问题,并且可以安全地使用 exports 来更简洁。

将其中任何一个分配给非对象时,它们现在指向不同的地方,这可能会造成混淆,除非您故意希望 module.exports 成为特定的东西(例如函数)。

exports 设置为非对象没有多大意义,因为您必须在最后设置 module.exports = exports 才能在其他文件中使用它。

let module = { exports: {} };
let exports = module.exports;

exports.msg = 'hi';
console.log(module.exports === exports); // true

exports = 'yo';
console.log(module.exports === exports); // false

exports = module.exports;
console.log(module.exports === exports); // true

module.exports = 'hello';
console.log(module.exports === exports); // false

module.exports = exports;
console.log(module.exports === exports); // true

为什么将 module.exports 分配给函数?

更简洁!比较第二个例子有多短:

module.exports.hello = () => console.log('hello world');

app1.js:let sayHello = require('./helloWorld1'); sayHello.hello; // hello world

module.exports = () => console.log('hello world');

app2.js:let sayHello = require('./helloWorld2'); sayHello; // hello world


R
Ryan Dhungel

https://i.stack.imgur.com/JKQNj.png

您创建的每个文件都是一个模块。模块是一个对象。它具有名为 exports : {} 的属性,默认情况下为空对象。

您可以创建函数/中间件并将其添加到这个空的导出对象,例如 exports.findById() => { ... } 然后 require 在您的应用中的任何位置并使用...

控制器/user.js

exports.findById = () => {
    //  do something
}

需要在 routes.js 中使用:

const {findyId} = './controllers/user'

B
Buddhi Nipun

在 node js 中,module.js 文件用于运行 module.load 系统。每次 node 执行文件时,它都会包装你的 js 文件内容,如下所示

'(function (exports, require, module, __filename, __dirname) {',+
     //your js file content
 '\n});'

由于这种包装在您的 js 源代码中,您可以访问导出、要求、模块等。使用这种方法是因为没有其他方法可以将 js 文件中写入的功能写入另一个文件。

然后节点使用 c++ 执行这个包装的函数。那时,传递给此函数的导出对象将被填充。

你可以在这个函数里面看到exports和module的参数。实际上,exports 是模块构造函数的公共成员。

看下面的代码

将此代码复制到 b.js

console.log("module is "+Object.prototype.toString.call(module));
console.log("object.keys "+Object.keys(module));
console.log(module.exports);
console.log(exports === module.exports);
console.log("exports is "+Object.prototype.toString.call(exports));
console.log('----------------------------------------------');
var foo = require('a.js');
console.log("object.keys of foo: "+Object.keys(foo));
console.log('name is '+ foo);
foo();

将此代码复制到 a.js

exports.name = 'hello';
module.exports.name = 'hi';
module.exports.age = 23;
module.exports = function(){console.log('function to module exports')};
//exports = function(){console.log('function to export');}

现在使用节点运行

这是输出

module is [object Object]
object.keys id,exports,parent,filename,loaded,children,paths
{}
true

出口是[对象对象]

foo 的 object.keys:名称为 function (){console.log('function to module exports')} function to module exports

现在删除 a.js 中的注释行并注释该行上方的行并删除 b.js 的最后一行并运行。

在javascript世界中,您不能重新分配作为参数传递的对象,但是当该函数的对象设置为另一个函数的参数时,您可以更改函数的公共成员

记得

仅当您想在使用 require 关键字时获取函数时才使用 module.exports 。在上面的例子中,我们 var foo = require(a.js);你可以看到我们可以调用 foo 作为一个函数;

这就是节点文档的解释方式“导出对象是由模块系统创建的。有时这是不可接受的,许多人希望他们的模块成为某个类的实例。为此,将所需的导出对象分配给 module.exports。”


V
VimNing

module.exports 和exports 都指向同一个函数database_module(cfg) {...}。 1|变量 a, b; 2| a = b = function() { console.log("Old"); }; 3| b = function() { console.log("New"); }; 4| 5|一个(); // “旧” 6| b(); // "New" 你可以将第 3 行的 b 改为 a,输出是相反的。结论是:a和b是独立的。所以 module.exports = exports = nano = function database_module(cfg) {...} 等价于: var f = function database_module(cfg) {...};模块.exports = f;出口 = f;假设上面是module.js,这是foo.js所需要的。 module.exports = exports = nano = function database_module(cfg) {...} 的好处现在很清楚了:在 foo.js 中,因为 module.exports 是 require('./module.js'): var output = require ('./modules.js')();在 moduls.js 中:您可以使用导出而不是 module.exports。

因此,如果 exportsmodule.exports 都指向同一事物,您会很高兴。


T
Thofiq

导出:它是对 module.exports 对象的引用

export 和 module.exports 都指向同一个对象,直到我们更改 export 对象的引用

例子:

如果exports.a = 10 然后module.exports.a = 10 如果我们在代码中明确地重新分配exports 对象,例如exports = {} 现在它失去了对module.exports 的引用