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

333
在这个页面上 (http://docs.nodejitsu.com/articles/getting-started/what-is-require),它说明了“如果你想将exports对象设置为一个函数或一个新对象,那么你必须使用module.exports对象。”我的问题是为什么。
// right
module.exports = function () {
  console.log("hello world")
}
// wrong
exports = function () {
  console.log("hello world")
}

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

您能解释一下原因吗?我在这里阅读了文章:module.exports vs exports in Node.js。这很有帮助,但并没有解释为什么它被设计成这样。如果直接返回exports的引用,会出现问题吗?


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

734

module是一个普通的JavaScript对象,其中包含一个exports属性。exports是一个普通的JavaScript变量,它恰好被设置为module.exports。 在文件末尾,Node.js基本上会将module.exports“返回”给require函数。在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不再是同一个对象。

21
没问题,这只是引用类型的基础知识。 - Vitalii Korsakov
7
很好的解释。module.exports的文档也有说明:https://nodejs.org/api/modules.html#modules_module_exports - Brian Morearty
2
这里有关于exports的文档:https://nodejs.org/api/modules.html#modules_exports_shortcut,它让我对exports有了更深入的理解。 - apollo
谢谢,但是为什么我们需要同时使用exportsmodule.exports呢?难道我们不能只使用后者,以简化代码(遵循KISS原则)吗? - zambotn
1
没有严格需要使用 exports,它只是一个方便的简写。但现在它永远不能被删除,因为这将破坏所有现有的代码。 - goto-bus-stop

80

朗润西的回答解释得很清楚。以下是一个带有示例的额外回答:

Node对您的文件进行了许多操作,其中一个重要的操作是包装您的文件。在nodejs源代码中,返回的是"module.exports"。让我们退后一步,了解一下包装器。假设你有

greet.js

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

module.exports = greet;

以上代码被封装为IIFE(立即调用函数表达式)并嵌入Node.js源代码中,如下所示:

(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().... 赋给 exports 时使用 "=",这会在内存中创建另一个对象。因此,module.exports 和 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指向相同的引用,它是一个函数。如果在exports上设置属性,则会在module.exports上设置该属性,因为在JS中,对象是按引用传递的。

结论是始终使用module.exports以避免混淆。希望这可以帮助到你。愉快的编码:)


这也是一个美丽而有见地的答案,与 @goto-bus-stop 的回答相得益彰。 :) - varun

30

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

math.js

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

client.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。

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

math.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。

另一种方法是:

math.js

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

Or:

math.js

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

17

Rene 关于 exportsmodule.exports 之间关系的回答十分清晰,它涉及到 JavaScript 中引用的问题。只想补充一点:

我们在许多 Node 模块中都可以看到这样的写法:

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

这将确保即使我们更改了 module.exports,我们仍然可以使用 exports,因为这两个变量都指向同一个对象。


我对这个解释感到困惑,能否详细说明一下? - GuyFreakz
6
@GuyFreakz,我不确定这是否解决了你的困惑,但是module.exportsexports只是分别初始化为引用同一个对象的不同变量。如果您更改一个变量引用的内容,则两个变量不再引用相同的内容。上面的代码行确保两个变量都初始化为相同的新对象。 - Andrew Palmer
一个实际的用例,其他人都错过了 @fengshuo。谢谢! - Aakash Verma

5

Node会执行类似以下的操作:

module.exports = exports = {}

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

这样做只是为了方便。 所以,代替写下如下内容:

module.exports.PI = 3.14

我们可以编写

exports.PI = 3.14

因此往输出exports中添加属性是可以的,但是将其分配给另一个对象则不行。

exports.add = function(){
.
.
} 

↑ 这是正确的,与 module.exports.add = function(){...} 相同。

exports = function(){
.
.
} 

↑ 这是不可取的,因为 module.exports 仍然指向 {},而 exports 指向另一个对象。


2

module.exportsexports之间有两个不同点:

  1. 当从一个模块导出单个类、变量或函数到另一个模块时,我们使用module.exports。但是如果要导出多个变量或函数,则使用exports

  2. module.exports是从require()调用返回的对象引用。但是exports不会被require()返回。

查看更多详细内容和示例 >> 链接


1
作为以上所有回答都很好地解释了,我想补充一些今天我遇到的事情。
当您使用exports导出某些内容时,必须使用变量来使用它。例如,

File1.js

exports.a = 5;

在另一个文件中。

File2.js

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

并且使用 module.exports


File1.js

module.exports.a = 5;

在File2.js文件中。
const A = require("./File1.js");
console.log(A.a);

默认的module.exports

File1.js

module.exports = 5;

在 File2.js 文件中。
const A = require("./File2.js");
console.log(A);

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

我认为 MDN 提供的解释是最清晰的。

基本上,有一个在内存中的对象,由两个变量 exports 和 module.exports 引用相同的对象。

exports.a = 23

等于

module.exports = {a:23}

但是,

exports = {a:23}

不等于

module.exports = {a:23}

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

0

myTest.js

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

exports.put = function () {};

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

exportsmodule.exports 是相同的,指向同一个对象。您可以根据需要通过两种方式添加属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接