ChatGPT解决这个技术问题 Extra ChatGPT

CommonJs 模块系统中“module.exports”和“exports”的区别

在此页面 (http://docs.nodejitsu.com/articles/getting-started/what-is-require) 上,它声明“如果要将导出对象设置为函数或新对象,则必须使用 module.exports 对象。”

我的问题是为什么。

// right
module.exports = function () {
  console.log("hello world")
}
// wrong
exports = function () {
  console.log("hello world")
}

我控制台记录了结果 (result=require(example.js)),第一个是 [Function],第二个是 {}

你能解释一下背后的原因吗?我在这里阅读了这篇文章:module.exports vs exports in Node.js。它很有帮助,但没有解释为什么以这种方式设计的原因。如果直接返回exports的reference会不会有问题?

始终使用 module.exports
我认为遵循上述建议可以避免这个问题。
@GabrielLlamas 那么为什么许多软件包只使用 exports,例如 github.com/tj/consolidate.js/blob/master/lib/consolidate.js
@Imray 如果您始终使用 module.exports,您永远不会出错,但如果您不替换默认导出对象,则可以使用 exports,也就是说,如果您只是附加如下属性:var foo = require('foo').foo。此 foo 属性可以像这样导出:exports.foo = ...,当然也可以使用 module.exports。这是个人选择,但我目前正在适当地使用 module.exportsexports
我更喜欢 export.myFunc = function() {} 所以我不必在文件底部维护导出列表。在 ES6 中声明时感觉更接近于导出的常见做法。

g
goto-bus-stop

module 是具有 exports 属性的纯 JavaScript 对象。 exports 是一个纯 JavaScript 变量,恰好设置为 module.exports。在文件的末尾,node.js 基本上会“返回”module.exportsrequire 函数。在 Node 中查看 JS 文件的一种简化方法是:

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

// your code

return module.exports;

如果您在 exports 上设置一个属性,例如 exports.a = 9;,那么它也将设置 module.exports.a,因为对象在 JavaScript 中作为引用传递,这意味着如果您将多个变量设置为同一个对象,它们 都是同一个对象;那么 exportsmodule.exports 是同一个对象。
但是如果您将 exports 设置为新的东西,它将不再设置为 module.exports,因此 exportsmodule.exports 不再同一个对象。


对,这只是引用类型的基础知识。
很好的解释。 module.exports 的文档也对其进行了描述:nodejs.org/api/modules.html#modules_module_exports
这是导出的文档:nodejs.org/api/modules.html#modules_exports_shortcut,这给了我另一种理解
谢谢,但为什么我们同时需要 exportsmodule.exports?为了简单起见(和 KISS 原则),我们不能只接受(即)后者吗?
严格来说不需要 exports,它只是一个方便的速记。但是现在它永远不能被删除,因为它会破坏所有现有的代码。
S
Sdembla

蕾妮的回答很好解释。除了一个例子的答案:

Node 对你的文件做了很多事情,其中一个重要的事情就是包装你的文件。返回内部 nodejs 源代码“module.exports”。让我们退后一步,了解包装器。假设你有

问候.js

var greet = function () {
   console.log('Hello World');
};

module.exports = greet;

上面的代码在 nodejs 源代码中被包装为 IIFE(Immediately Invoked Function Expression),如下所示:

(function (exports, require, module, __filename, __dirname) { //add by node

      var greet = function () {
         console.log('Hello World');
      };

      module.exports = greet;

}).apply();                                                  //add by node

return module.exports;                                      //add by node

上面的函数被调用 (.apply()) 并返回 module.exports。此时module.exports和exports指向同一个引用。

现在,假设您将 greet.js 重写为

exports = function () {
   console.log('Hello World');
};
console.log(exports);
console.log(module.exports);

输出将是

[Function]
{}

原因是:module.exports 是一个空对象。我们没有为 module.exports 设置任何东西,而是在新的 greet.js 中设置了exports = function().....。所以,module.exports 是空的。

从技术上讲,exports 和 module.exports 应该指向相同的引用(这是正确的!!)。但是我们在将 function().... 分配给导出时使用“=”,这会在内存中创建另一个对象。因此, module.exports 和 exports 产生不同的结果。当涉及到导出时,我们不能覆盖它。

现在,假设你重写(这称为 Mutation)greet.js(指 Renee 答案)为

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

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

输出将是

{ a: [Function] }
{ a: [Function] }

如您所见, module.exports 和 exports 指向同一个引用,这是一个函数。如果您在导出上设置一个属性,那么它将在 module.exports 上设置,因为在 JS 中,对象是通过引用传递的。

结论总是使用 module.exports 来避免混淆。希望这可以帮助。快乐编码:)


这也是一个美丽而有见地的答案,并补充了 @goto-bus-stop 的答案。 :)
R
Rodrigo Branas

另外,可能有助于理解的一件事:

数学.js

this.add = function (a, b) {
    return a + b;
};

客户端.js

var math = require('./math');
console.log(math.add(2,2); // 4;

太好了,在这种情况下:

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

因此,默认情况下,“this”实际上等于 module.exports。

但是,如果您将实现更改为:

数学.js

var add = function (a, b) {
    return a + b;
};

module.exports = {
    add: add
};

在这种情况下,它可以正常工作,但是,“this”不再等于 module.exports,因为创建了一个新对象。

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

现在,require 将返回的是在 module.exports 中定义的内容,而不是 this 或 exports。

另一种方法是:

数学.js

module.exports.add = function (a, b) {
    return a + b;
};

或者:

数学.js

exports.add = function (a, b) {
    return a + b;
};

S
Siderite Zackwehdex

Rene 关于 exportsmodule.exports 之间关系的回答非常清楚,这完全是关于 javascript 引用。只想补充一点:

我们在许多节点模块中看到了这一点:

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

这将确保即使我们更改了 module.exports,我们仍然可以通过使这两个变量指向同一个对象来使用导出。


我对这个解释感到困惑,请详细说明?
@GuyFreakz 我不确定这是否让您感到困惑,但 module.exportsexports 只是单独的变量,初始化为引用同一个对象。如果您更改一个变量引用的内容,这两个变量将不再引用同一事物。上面的代码行确保两个变量都初始化为同一个新对象。
一个其他人在@fengshuo 上错过的实际用例。谢谢!
A
Ankit Aabad

节点做这样的事情:

module.exports = exports = {}

module.exports 和exports 指的是同一个对象。

这样做只是为了方便。所以不要写这样的东西

module.exports.PI = 3.14

我们可以写

exports.PI = 3.14

因此可以将属性添加到导出但将其分配给不同的对象是不行的

exports.add = function(){
.
.
} 

↑ 这没关系,与 module.exports.add = function(){...} 相同

exports = function(){
.
.
} 

↑ 这不行,并且空对象将作为模块返回。exports 仍然引用 {},exports 引用不同的对象。


J
Janitha Rasanga

module.exportsexports 之间有两个区别

当将单个类、变量或函数从一个模块导出到另一个模块时,我们使用 module.exports。但是从一个模块导出到多个变量或函数到另一个,我们使用导出。 module.exports 是从 require() 调用返回的对象引用。但是 require() 不会返回导出。

通过示例查看更多详细信息>> link


D
Dharman

由于上面发布的所有答案都得到了很好的解释,我想添加一些我今天遇到的问题。

当您使用导出导出某些内容时,您必须将其与变量一起使用。喜欢,

文件1.js

exports.a = 5;

在另一个文件中

文件2.js

const A = require("./File1.js");
console.log(A.a);

并使用 module.exports

文件1.js

module.exports.a = 5;

在 File2.js 中

const A = require("./File1.js");
console.log(A.a);

和默认 module.exports

文件1.js

module.exports = 5;

在 File2.js 中

const A = require("./File2.js");
console.log(A);

s
simonwo

myTest.js

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

exports.put = function () {};

console.log(module.exports)
// output: { get: [Function], put: [Function] }

exportsmodule.exports 是相同的并且是对同一个对象的引用。您可以根据自己的方便通过两种方式添加属性。


k
ktul

您可以将导出视为给定模块中 module.exports 的快捷方式。实际上,exports 只是一个变量,它在模块被评估之前被初始化为 module.exports 的值。该值是对对象的引用(在本例中为空对象)。这意味着 export 持有对 module.exports 引用的同一对象的引用。这也意味着通过为导出分配另一个值,它不再绑定到 module.exports。

MDN的这个解释对我来说是最清楚的。

基本上,内存中有一个对象被 2 个变量引用——exports 和 module.exports。

exports.a = 23

等于

module.exports = {a:23}

但,

exports = {a:23}

不等于

module.exports = {a:23}

当您直接将新对象分配给 exports 变量时,该变量不再引用 module.exports