node.exports与Node.js中的导出

时间:2011-08-21 09:16:56

标签: javascript node.js commonjs

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

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

我想知道module.exportsexports之间的区别是什么,为什么两者都在这里使用。

23 个答案:

答案 0 :(得分:430)

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

你可以想象,在你的文件的最开始,有一些东西(仅用于解释):

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

enter image description here

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

所以当你做类似的事情时:

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

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

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

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

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

现在返回的结果类型为'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?

答案 1 :(得分:395)

设置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可以保存字符并避免混淆。

答案 2 :(得分:200)

基本上答案在于通过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的一部分导出或返回。

答案 3 :(得分:75)

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

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

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

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

答案 4 :(得分:46)

除非您在模块中重新分配exports,否则

module.exportsexports都是相同的。

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

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() { ... }

答案 5 :(得分:24)

JavaScript通过引用副本

传递对象

与JavaScript中引用传递对象的方式有细微差别。

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

说我写这样的东西:

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

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

当导入功能检查module.exports时,它会{b:12}

答案 6 :(得分:11)

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

答案 7 :(得分:9)

以下是 manning 出版物中 node.js in action 一书中关于节点模块的精彩描述。
最终在您的应用程序中导出的是 module.exports。
exports
已设置 简单地作为 module.exports 的全局引用,最初被定义为 您可以添加属性的空对象。所以 exports.myFunc 只是简写 for module.exports.myFunc

因此,如果将 exports 设置为其他任何内容,则会破坏引用 module.exports exports 。因为 module.exports 才是真正得到的 export, exports 将不再按预期工作 - 它不会引用模块 .exports 了。如果要维护该链接,可以创建 module.exports 再次参考出口

ExecutorService es = ...;
Future t1work = new Future(...);
Future t2work = new Future(...);
Future t2update = new Future(...);

es.submit(t1work);
es.submit(t2work);

while (1) {
  if (t1work.isDone() && t2work.isDone())   es.submit(t2update);
  if (t1work.isDone() && t2update.isDone()) es.submit(t1work);
  if (t2work.isDone() && t2update.isDone()) es.submit(t2work);
  Thread.sleep(100);
}

答案 8 :(得分:6)

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

<强> 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内部发生了什么,但如果您能更好地理解这一点,请发表评论,因为我确信它会有所帮助。

- 快乐编码

答案 9 :(得分:3)

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

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应该没问题

答案 10 :(得分:3)

来自docs

  

exports变量在模块的文件级范围内可用,并在评估模块之前分配module.exports的值。

     

它允许使用快捷方式,因此module.exports.f = ...可以更简洁地编写为exports.f = .... 但是,要注意像任何变量一样,如果为导出分配了一个新值,它不再绑定到module.exports:

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

答案 11 :(得分:3)

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

答案 12 :(得分:3)

这是

的结果
console.log("module:");
console.log(module);

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

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

enter image description here

此外:

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

//YES

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

答案 13 :(得分:3)

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

<强>问题 模块不可能直接导出除导出对象之外的值,例如函数。例如,模块可能只想导出它定义的对象类型的构造函数。现在,它无法做到这一点,因为要求始终使用它创建的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);

答案 14 :(得分:2)

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

答案 15 :(得分: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')}

答案 16 :(得分:1)

1.exports - &gt;用作单身实用程序
2. module-exports - &gt;用作服务,模型等逻辑对象

答案 17 :(得分:1)

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

单程

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

答案 18 :(得分:1)

enter image description here

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

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

controllers / user.js

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

routes.js 中要求使用:

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

答案 19 :(得分:0)

节点js中的

module.js文件用于运行module.load系统。每当节点执行一个文件时,它包装你的js文件内容如下

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

因为这个包装在ur js源代码中你可以访问export,require,module等。 使用这种方法是因为没有其他方法可以将js文件中的功能写入另一个。

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

你可以在里面看到这个函数参数export和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

exports是[object Object]

foo的object.keys: name是function(){console.log('function to module exports')} 功能到模块导出

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

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

记得

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

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

答案 20 :(得分: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"
    

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

      

    ab是独立的。

  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

  3. 所以,如果exportsmodule.exports指向相同的事情,你会很高兴。

答案 21 :(得分:0)

  

为什么两者都在这里使用

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

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

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

module.exports vs exports

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

当分配给非对象时,它们现在指向可能令人困惑的不同位置,除非您故意希望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 分配给函数?

更简洁!比较第二个例子的缩短程度:

helloWorld1.js:module.exports

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

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

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

答案 22 :(得分:0)

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

当您的模块在另一个使用module.exports语句的模块中使用时,添加到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 website has a very good explanation of this.