Node.js中的module.exports与export


725

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

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

我不知道什么之间的不同module.exportsexports为什么都被用在这里。





8
这都是关于参考的。将导出想像成指向module.exports的局部变量对象。如果覆盖了export的值,则会丢失对module.exports的引用,而module.exports是公开为公共接口的内容。
2015年

14
快速摘要:exportsmodule.exports指向同一个对象,除非你重新分配一个。并最终module.exports返回。因此,如果您重新分配exports给函数,则不要期望该函数,因为它不会被返回。但是,如果您分配了这样的函数,exports.func = function...则结果将具有func属性,并将函数作为值。因为您将属性添加到了exports指向.. 的对象上
Muhammad Umer

Answers:


426

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

module.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保存字符并避免混淆。


8
@ajostergaard:它恰好是OP的示例所来自的的名称。在该模块中,它允许作者编写诸如nano.version = '3.3'代替的内容module.exports.version = '3.3',从而使阅读更加清晰。(请注意,这nano是一个局部变量,在设置模块导出之前已声明。)
josh3736

3
@lime-谢谢-我很高兴这基本上无关紧要,因为如果不是,那意味着我会完全误解了一切。:-| :)
ostergaard 2013年

嗨,莱姆,这是一个很老的答案,但我希望您能澄清一下。如果我要设置module.exports不是 设置exports,我的代码是否仍然可以工作?谢谢你的帮助!
Asad Saeeduddin

1
@Asad是,只要您设置,该函数将正确导出module.exports
Lime

@Liam感谢您的宝贵回答。其他几个查询-在server.js条目中,module.exports和export的值应该是什么?是module.exports应该为空,并且导出设置为空对象?是这种传统还是存在一些有效的用例,可以将export和module.exports指向两个不同的对象?
Sushil

504

即使问题早已得到回答和接受,我也只想分享我的2美分:

您可以想象,在文件的开头有类似的内容(仅供说明):

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

在此处输入图片说明

因此,无论您做什么,都请记住,当您从其他地方需要该模块时module.exports,NOT exports将不会从模块中返回。

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

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

你加入2功能ab对象上module.exports的点太多,所以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,因此exports和之间module.exports不再存在关系。在这种情况下module.exports仍然指向{}将要返回的空对象。

接受的另一个主题的答案也应该有所帮助: Javascript是否通过引用传递?


2
很好的解释,但我仍然不明白如何完全忽略module.exports模块,例如在此npm软件包中:github.com/tj/consolidate.js/blob/master/lib/consolidate.js
CodyBugstein

4
@Imray说明在这里:JavaScript是否通过引用传递? exports.a = function(){}; works, exports = function(){} doesn't work
cirpo 2015年

29
oooo这个答案终于解释了。基本上,导出是指可以向其添加属性的对象,但是如果重新分配为功能,则不再需要将属性附加到该原始对象。现在,当module.exports仍指向该对象时,导出将引用函数,因为这是返回的内容。您可以说出口基本上已经被垃圾收集了。
穆罕默德·乌默尔

5
那么,使用的目的是exports什么?module.exports如果只是可变的重新分配,为什么不总是使用呢?似乎让我感到困惑。
jedd.ahyoung

1
@ jedd.ahyoung exports.something而不是module.exports.something
Srle

209

基本上,答案在于通过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 = [];
}

如您所见,每个模块对象都有一个名为name的属性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的一部分导出或返回。


8
在这里迷失了我exports = module.exports = {};
巨麋鹿2015年

2
我认为这应该是最好的答案,它解释了为什么func()@William的答案失败!
乌龟鸽2015年

2
我看不到exports = module.exports = app;在代码的最后一行添加任何优势 。看来module.exports将会被导出,我们将永远不会使用exports,因为它再次位于代码的最后一行。所以,为什么我们不直接添加module.exports = app;
lvarayut

79

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

如果我们向对象添加属性,例如exports.a=1,则module.exports和export 引用同一对象。因此,如果我们调用require并将模块分配给变量,则该变量具有a属性,其值为1;

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

对我而言,我将继续添加新属性,或将它们都覆盖到新对象中。仅仅覆盖一个是不对的。并记住那module.exports才是真正的老板。


1
是的,这实际上是真正的答案。简洁明了。其他人可能是正确的,但充满幻想的术语,而不是完全专注于该问题的答案。
Khoa

到目前为止,这是最清楚的答案!在你想收藏它的情况下,这是确切联系:stackoverflow.com/questions/7137397/...
lambdarookie

56

exports并且module.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() { ... }

9
这是所有答案中最容易理解的!
Adarsh Konchady '16

2
简洁明了
fibono

1
了解此功能的简便方法。
FilipeCanatto

27

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

在JavaScript中通过引用传递对象的方式有微妙的区别。

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

说我写这样的东西:

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

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

导入功能检查module.exports时得到{b:12}


6
最好的答案恕我直言!
AJ先生

1
“ JavaScript通过引用传递” –
xehpuk '18

13

我只是做一些测试,结果发现,在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)

莱曼(Lyman),这module.exports是节点处理掉的“实际交易”,但有时您需要将所有内容添加exports到其中,module.exports除非您使用的是exports.namespace(上述情况2),在这种情况下,节点是否在对象上extends(module.exports, exports);添加了所有的“命名空间” ?换句话说,如果您正在使用,则可能要在其上设置属性?exportsmodule.exportsexports
科迪2014年

11

这是Manning出版的操作手册中有关node.js中的节点模块的良好描述。 最终在您的应用程序中导出的是module.exports。可以将exports设置为对module.exports的全局引用,该模块最初定义为可以向其添加属性的空对象。所以exports.myFunc是刚刚速记module.exports.myFunc。 其结果是,如果出口被设置为别的,它打破了基准之间 module.exports出口。因为module.exports



是真正要导出的内容,导出将不再按预期工作-它不再引用模块.exports。如果要维护该链接,可以使module.exports再次 引用导出,如下所示:

module.exports = exports = db;

8

我经过了一些测试,我认为这可以为这个问题提供一些启示。

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:{},用户:{}}”


./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.js{ ThisLoadedLast: [Function: ThisLoadedLast] },我们得到的输出“@routes {ThisLoadedLast:[功能: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内部的情况,但是请评论一下,如果您能对此有所了解,我相信它会有所帮助。

-快乐的编码


我认为它们不必要地复杂和混乱。它应该透明和直观。
ngungo 2014年

我同意。在某些情况下,它可能对命名间隔很有用,但通常不会造成任何损坏。
科迪2014年

4

这展示了如何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 is a string”。该对象仅作为参考存在。这是我直到现在才真正意识到的行为。
danielgormly

4

这是结果

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

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

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

在此处输入图片说明

也:

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

//YES

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


4
var a = {},md={};

//首先,exports和module.exports指向相同的空对象

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); //{}

4

来自文档

导出变量在模块的文件级范围内可用,并在评估模块之前为其指定了module.exports的值。

它允许使用快捷方式,因此module.exports.f = ...可以更简单地编写为export.f =...。但是,请注意,就像任何变量一样,如果将新值分配给exports,不再绑定到module.exports:

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



3

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


3

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

module.exports 当您的模块在另一个模块中使用using require语句使用时,添加到对象的任何属性都将可用。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网站对此有很好的解释。


2

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


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;
}

2

为什么都在这里使用

我相信他们只是想明确的是module.exportsexportsnano指向同一个功能-允许你使用任何一个变量来调用该文件中的函数。nano提供函数功能的一些上下文。

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

详细程度的折衷限制了将来发生错误的风险,例如在文件中使用exports代替module.exports。它还提供澄清module.exportsexports实际上指向相同的值。


module.exportsexports

只要您不重新分配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功能?

更简洁!比较第二个示例短了多少:

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

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

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

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


2

在此处输入图片说明

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

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

控制器/user.js

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

routes.js中要求使用:

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

2

要了解差异,您必须首先了解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')}

0

节点js中的module.js文件用于运行module.load system。每次节点执行文件时,它都会将您的js文件内容包装如下

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

由于此包装在ur js源代码中,因此您可以访问export,require,module等。之所以使用此方法,是因为没有其他方法可以将在js文件中写入的功能复制到另一个文件中。

然后节点使用c ++执行此包装函数。届时将填充传递到此函数的导出对象。

您可以在此函数中看到参数导出和模块。实际上,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导出')} function to module export

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

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

记得

仅在要使用require关键字时要获得功能时,才使用module.exports。在上面的示例中,我们var foo = require(a.js); 您可以看到我们可以将foo作为函数调用;

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


0
  1. 双方module.exportsexports指向相同的function database_module(cfg) {...}

    1| var a, b;
    2| a = b = function() { console.log("Old"); };
    3|     b = function() { console.log("New"); };
    4|
    5| a(); // "Old"
    6| b(); // "New"

    您可以将b第3行更改为a,输出为反向。结论是:

    a并且b是独立的。

  2. 因此module.exports = exports = nano = function database_module(cfg) {...}等于:

    var f = function database_module(cfg) {...};
    module.exports = f;
    exports = f;

    假设是module.js,这是必需的foo.js。现在的好处module.exports = exports = nano = function database_module(cfg) {...}显而易见:

    • 在中foo.js,因为module.exportsrequire('./module.js')

      var output = require('./modules.js')();
    • moduls.js:您可以使用exports代替module.exports

所以,你会如果既开心exportsmodule.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.