Node.jsmodule.exportsvs出口

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

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

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

331708 次浏览

设置module.exports允许在required时像函数一样调用database_module函数。简单地设置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.exportsfunc();

基本上,node.js不导出exports当前引用的对象,而是导出exports最初引用的对象的属性。虽然Node.js确实导出了对象module.exports的引用,允许您像函数一样调用它。


第二个最不重要的原因

他们同时设置module.exportsexports以确保exports没有引用先前导出的对象。通过设置两者,您使用exports作为速记,并避免以后的潜在错误。

使用exports.prop = true而不是module.exports.prop = true可以节省字符并避免混淆。

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

http://timnew.me/blog/2012/04/20/exports-vs-module-exports-in-node-js/

要添加到其他帖子节点中的模块系统做

var exports = module.exports

因此,当您想要导出=foo时,您可能想要执行module.exports=导出=foo,但使用exports.foo=foo应该没问题

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

需要的下一步是将file1.js的内容包装成一个匿名函数,如下所示:

(function (exports, require, module, __filename, __dirname) {//contents from file1.jsmodule.exports = '123;});

这个匿名函数是通过以下方式调用的,这里的module指的是前面创建的Module对象。

(function (exports, require, module, __filename, __dirname) {//contents from file1.jsmodule.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对象产生任何影响,并且不会导出或返回任何内容作为需求的一部分。

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

如果我们添加属性指向对象,例如exports.a=1,那么module.exports和导出仍然指向同一个对象。因此,如果我们调用需要并将模块分配给一个变量,那么该变量具有属性a并且其值为1;

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

对我来说,我会不断添加新的属性,或者将它们都覆盖到一个新对象中。仅仅覆盖一个是不对的。记住module.exports是真正的老板。

我只是做了一些测试,事实证明,在nodejs的模块代码中,它应该是这样的:

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

所以:

1:

exports = function(){}; // this will not work! as it make the exports to some other pointermodule.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'; // worksexports.b = 'value b'; // the b will nerver be seen cause of the first line of code we have do it before (or later)

在node js中module.js文件用于运行module.loadsystem.every当node执行文件时,它将js文件内容包装如下

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

由于这种包装在ur js源代码中,您可以访问导出、需求、模块等。使用这种方法是因为没有其他方法可以将写在js文件上的功能写入另一个。

然后节点使用c++执行这个包装的函数。此时,传递给这个函数的导出对象将被填充。

您可以在这个函数中看到参数导出和模块。实际上导出是模块构造函数的公共成员。

查看以下代码

将此代码复制到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:方法名称:函数(){console.log('函数到模块导出')}模块导出的函数

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

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

确实记得

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

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

“如果你希望模块导出的根是一个函数(例如构造函数),或者如果你想在一个赋值中导出一个完整的对象,而不是一次构建一个属性,请将其分配给module.exports而不是导出。

我经历了一些测试,我认为这可能会揭示这个问题…

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

我们得到的输出是“@路线{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(){};

我们得到输出“@路线{用户:[函数:用户]}”如果我们将user.js更改为{ ThisLoadedLast: [Function: ThisLoadedLast] },我们会得到输出“@路由{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(){};

…我们得到“@路线{fn:{fn:[Function: fn]}, ThisLoadedLast:{ThisLoadedLast:[Function: ThisLoadedLast]}}”

因此,我建议在模块定义中始终使用module.exports

我不完全理解Node内部发生了什么,但如果你能更多地理解这一点,请发表评论,因为我相信这会有所帮助。

--编码愉快

尽管问题已经回答并接受了很久以前,我只想分享我的2美分:

您可以想象在文件的开头有这样的内容(只是为了解释):

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

输入图片描述

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

当你做这样的事情:

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

您正在向module.exports指向的对象添加2个函数ab,因此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指向的对象,所以exportsmodule.exports之间不再有关系。在这种情况下,module.exports仍然指向将返回的空对象{}

从另一个主题接受的答案也应该帮助:JavaScript引用传递?

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

这与JavaScript中通过引用传递对象的方式有细微的区别。

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

假设我写这样的东西:

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

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

当导入函数检查module.exports时,它会得到{b:12}

这是对曼宁出版物中node.js在行动书中的节点模块的很好描述。
最终在您的应用程序中导出的是module.exports.
出口
设置up简单地作为对 module.exports 的全局引用,最初定义为可以添加属性的空对象。所以exports.myFunc只是速记module.exports.myFunc.

因此,如果出口被设置为其他任何值,它会破坏 参考之间的module.exports出口 。因为 module.exports是真正得到的导出,出口将不再按预期工作-它不引用模块.导出了。如果你想保持这个链接,你可以 module.exports再次引用 出口 如下:

module.exports = exports = db;

这显示了require()如何以最简单的形式工作,摘自雄辩的JavaScript

<强>问题模块不能直接导出导出对象以外的值,例如函数。例如,模块可能只想导出它定义的对象类型的构造函数。现在,它不能这样做,因为需要总是使用它创建的exports对象作为导出值。

解决方案为模块提供另一个变量module,它是一个具有属性exports的对象。此属性最初指向由需要创建的空对象,但可以用另一个值覆盖以导出其他内容。

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

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

这是的结果

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

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

1.exports->用作单例实用程序
2、模块导出->作为服务、模型等逻辑对象使用

var a = {},md={};

//首先,导出和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); //{}

文档

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

它允许快捷方式,因此module.exports.f=…可以更简洁地写成导出. f=…。但是,请注意,与任何变量一样,如果将新值分配给导出,它将不再绑定到module.exports:

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

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

一种方式

var aa = {a: () => {return 'a'},b: () => {return 'b'}}
module.exports = aa;

第二种方式

exports.a = () => {return 'a';}exports.b = () => {return 'b';}

这就是需要()将如何集成模块。

第一种方式:

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;}
  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')();
    • In moduls.js: You can use exports instead of module.exports.

So, you will be happy if both exports and module.exports pointing to the same thing.

为什么两者都在这里使用

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

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

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


module.exports vsexports

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

在此处输入图片描述

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

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

控制器/user.js

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

需要在routes.js中使用:

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

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

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

要理解这些差异,你必须首先理解Node.js在运行时对每个模块做了什么。Node.js为每个模块创建一个包装函数:

 (function(exports, require, module, __filename, __dirname) {
})()

注意第一个参数exports是一个空对象,第三个参数module是一个具有许多属性的对象,其中一个属性被命名为exports。这就是exports的来源,module.exports的来源。前者是一个变量对象,后者是module对象的属性。

在模块中,Node.js在开始时自动执行此操作:module.exports = exports最终返回#1。

所以你可以看到,如果你将某个值重新分配给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')}
  • 出口:它是对module.exports对象的引用
  • 出口module.exports都指向同一个对象直到我们改变出口对象的引用

示例:

  1. 如果exports. a=10module.exports.a=10

  2. 如果我们在代码中显式地重新分配导出对象,例如现在它失去了对module.exports的引用