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


276

在此页面(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")
}

我console.logged结果(result=require(example.js))和第[Function]一个是{}

您能否解释其背后的原因?我在这里阅读了这篇文章:Node.js中的module.exports与export。它很有帮助,但没有说明采用这种方式进行设计的原因。如果直接返回出口参考书会不会有问题?


11
始终使用module.exports
加布里埃尔·拉马斯

1
我认为遵循上述建议可以避免此问题。
Vitalii Korsakov 2013年

@GabrielLlamas所以为什么许多软件包只使用exports,例如github.com/tj/consolidate.js/blob/master/lib/consolidate.js
CodyBugstein 2015年

3
@Imray如果您始终使用module.exports,那么您永远不会错,但是exports如果您不替换默认的导出对象,也就是简单地附加如下属性,就可以使用var foo = require('foo').foofoo可以这样导出该属性:exports.foo = ...当然也可以使用导出module.exports。这是个人选择,但我目前正在使用module.exports并且exports适当。
加布里埃尔·拉马斯

我更喜欢exports.myFunc = function(){},因此我不必在文件底部维护一个导出列表。在ES6中进行声明时,感觉更接近于导出的常规做法。
SacWebDeveloper

Answers:


624

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不再是同一个对象。


11
是的,这只是引用类型的基础。
Vitalii Korsakov

18
为什么!?为什么只在这里可以阅读此内容。对于每个模块化javaScript,这应该是标语。谢谢
lima_fil

8
精美的解释!
Aakash Verma

3
很棒,最好的答案!!
约翰

5
很好的解释。该文档的文档module.exports对此
Brian Morearty,

52

蕾妮的答案得到了很好的解释。除示例外,还提供答案:

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

greet.js

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

module.exports = greet;

上面的代码包装为nodejs源代码中的IIFE(立即调用函数表达式),如下所示:

(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和export指向相同的引用。

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

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

输出将是

[Function]
{}

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

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

现在,假设您将greet.js(指的是Renee答案)重写为(称为Mutation)为

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的答案。:)
varun

23

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

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将返回的是模块内部定义的内容。exports不再是this或exports。

另一种方法是:

math.js

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

要么:

math.js

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

15

刘若英的约之间的关系答案exportsmodule.exports是相当清楚的,它是所有关于JavaScript的引用。只需添加:

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

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

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


我对这种解释感到困惑,可以详细说明吗?
GuyFreakz

6
@GuyFreakz我不知道这是否说你的困惑,但module.exportsexports都只是不同的变量,初始化为引用同一个对象。如果更改一个变量引用的内容,则两个变量将不再引用相同的内容。上面的代码行确保两个变量都初始化为同一新对象。
安德鲁·帕尔默

其他人在@fengshuo上错过的一个实际用例。谢谢!
Aakash Verma

0

myTest.js

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

exports.put = function () {};

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

exports并且module.exports是相同的,并且是对同一对象的引用。您可以根据需要通过两种方式添加属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.