module.exports vs exports之间的模块和区别是什么?

时间:2014-01-31 01:45:09

标签: javascript node.js

我几个小时以来一直在阅读这个主题,而且还没有发现任何可以帮助制作这个主题的内容。

模块只是节点中的一个对象,具有一些属性,一个是引用对象的exports属性。

'exports'变量是

var exports = module.exports

它是指向module.exports引用的对象的var。

我正在努力想象的是模块是什么。我知道这是一个对象,但只有一个吗?

我知道这不是节点实现模块的确切方式,但我可视化它看起来像这样:

var module = {}

module.exports = {}

// now module has a property module.exports

var exports = module.exports

现在,从我一直在阅读的所有内容中,如果你要为module.export ='xyz'分配一些东西

它将保持值'xyz'。它会丢失原始对象吗?最重要的是,如果我在同一个文件中为module.exports分配了其他内容,是否会将其替换为新值?

EX: 

// file = app.js

module.export = 'hello'
module.export = 'bye'

// file = newApp.js

require(./app);

模块的价值是多少?我是否覆盖了相同的模块对象,还是有多个?

3 个答案:

答案 0 :(得分:7)

在我们继续之前,了解how modules are actually loaded by node非常重要。

从节点的模块加载系统中取消的关键是,在它实际运行代码之前require(在Module#_compile中发生),它creates a新的,空的exports对象作为Module的属性。 (换句话说,您的可视化是正确的。)

然后使用匿名函数节点wraps require d文件中的文本:

(function (exports, require, module, __filename, __dirname) {
// here goes what's in your js file
});

......基本上eval是那个字符串。 eval该字符串的结果是一个函数(匿名包装器),并且节点立即调用函数,传入如下参数:

evaledFn(module.exports, require, module, filename, dirname);

requirefilenamedirname是对require函数(实际为isn't a global)的引用,以及包含已加载模块的字符串&#39 ; s路径信息。 (这就是docs的含义,当他们说" __filename实际上并非全局而是每个模块都是本地的。")

所以我们可以在模块中看到exports === module.exports。但为什么模块同时获得moduleexports

向后兼容性。在节点的早期阶段,模块内部没有module变量。您只需分配到exports即可。但是,这意味着您永远不能将构造函数导出为模块本身。

一个熟悉的模块导出构造函数作为模块的例子:

var express = require('express');
var app = express();

这是因为Express exports a function by reassigning module.exports,会覆盖节点默认提供的空对象:

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

但请注意,您不能写:

exports = function { ... }

这是因为JavaScript's parameter passing semantics are weird。从技术上讲,JavaScript可能被视为"纯粹的按值传递,"但实际上参数是通过"按值引用"。

这意味着当您将对象传递给函数时,它会将该对象的引用作为值接收。您可以通过该引用访问原始对象,但无法更改调用者对引用的感知。换句话说,没有" out"你可能会在C / C ++ / C#中看到param。

作为一个具体的例子:

var obj = { x: 1 };

function A(o) {
    o.x = 2;
}
function B(o) {
    o = { x: 2 };
}

调用A(obj);将导致obj.x == 2,因为我们访问了以o传入的原始对象。但是,B(obj);将无所作为; obj.x == 1。将全新对象分配给B的本地o只会更改o指向的内容。它对呼叫者的对象obj没有任何作用,它仍然不受影响。

现在应该明白为什么因此需要将module对象添加到节点模块'当地范围。为了允许模块完全替换exports对象,它必须作为对象传递给模块匿名函数的属性。显然,没有人想破坏现有代码,因此exports被留作module.exports的引用。

因此,当您只为导出对象分配属性时,使用exports还是module.exports并不重要;它们是同一个,是指向完全相同的对象的参考。

仅当您要将某个功能导出为必须使用module.exports的顶级导出时,才会这样做,因为正如我们所见,只需指定exports的函数在模块范围之外没有任何影响。

最后请注意,当您将某个功能导出为模块时,最好将分配给 exportsmodule.exports。这样,两个变量保持一致,并且与标准模块中的变量一样。

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

请务必在模块文件的顶部附近执行此操作,以便不会有人意外地分配到最终被覆盖的exports

另外,如果你看起来很奇怪( = s?),我会利用包含assignment operator的表达式返回的事实指定的值,可以在一次拍摄中为单个值分配单个值。

答案 1 :(得分:2)

你正在覆盖模块 - 导出是一个单独的对象被require拉入。通常,在使用require执行此类模块化JavaScript时,导出将是构造函数,而不是示例中的字符串。这样,您就可以创建在模块中定义的新功能实例。例如:

// module.js
var MyConstructor = function(prop) {
    this.prop = prop;
});

module.exports = MyConstructor;

// app.js
var MyConstructor = require('module');
var instance = new MyConstructor('test');
console.log(instance.prop) // 'test'

答案 2 :(得分:2)

Node.js中的模块只是文件。每个文件都是一个模块,每个模块都是一个文件。如果您有多个文件,则可以有多个模块(每个文件一个)。

根据module.exportsthe Node.js API documentation将阐明该主题:

  

module.exports对象由Module系统创建。有时   这是不可接受的;许多人希望他们的模块成为一个实例   一些课。为此,请将所需的导出对象指定给   module.exports。请注意,将所需对象分配给exports将会   只需重新绑定本地exports变量,这可能不是什么   你想做什么。

  

模块中可用的exports变量以a开头   参考module.exports。与任何变量一样,如果您指定一个新变量   它的值,它不再绑定到以前的值。 ......作为指导,   如果export和module.exports之间的关系   对你来说似乎很神奇,忽略导出并只使用module.exports。

那么,这一切意味着什么?在您的特定示例中,module.exports将等于其最后指定的值('bye')。