首先,我不清楚为什么要在Node.js中使用“模块模式”。我的意思是,您建议的模块模式在客户端Javascript(即浏览器中的Javascript)中更有意义,但是如果您的代码旨在在Node.js中运行,那么您可以利用Node中已经存在的模块功能在这种情况下,我个人认为强制使用模块模式没有任何价值。
因此,我将首先以不同的方式来研究如何使用Node.js模块模式,最后,我将说明如何组合“模块模式”和“ Node.js模块模式”。
在模块上,导入和导出
让我们从最显而易见,最简单的事情开始。自从使用Node的第一天起,每个人可能都学到了一些东西:每个代码文件都被视为一个模块。我们在其中声明的变量,属性,函数,构造函数是该模块私有的,其他模块无法访问或使用它们,除非该模块的程序员明确将其公开。也就是说,除非另有明确说明,否则默认情况下,我们在模块内部声明的所有内容都被封装并从外界隐藏。为了公开某些内容,程序员可以访问名为的特殊对象
module,该对象具有名为的特殊属性
exports。您在
module.exports对象中发布的所有内容都可公开提供给其他模块。例如,在下面的代码中,变量
pi是其他任何模块都无法访问的,但是
foo.js命名属性
bar对于导入该模块的任何其他模块都是公开可用的
foo.js。请注意,与在浏览器中执行的Javascript相比,这是与Node.js中的Javascript的根本区别,在浏览器中,Javascript文件中的函数可能会在全局对象(即
window)中公开。
//module foo.jsvar pi = 3.14;module.exports.bar = 'Hello World';
现在,第二个模块
baz.js可以“导入”该模块
foo.js并访问该属性
bar。在Node中,我们通过使用名为的全局函数来实现此效果
require。如下所示:
//module baz.jsvar foo = require('./foo');console.log(foo.bar); //yields Hello World技术1 –扩展具有其他功能的导出对象
因此,一种在模块中公开功能的技术包括向
module.exports对象添加功能和属性。在这种情况下,Node提供了对导出对象的直接访问,使我们的事情变得更简单。例如:
//module foo.jsexports.serviceOne = function(){ };exports.serviceTwo = function(){ };exports.serviceThree = function(){ };如您所料,导入此模块时,该模块的用户将获得对该
exports对象的引用,从而可以访问其中公开的所有功能。
//module bar.jsvar foo = require('./foo');foo.serviceOne();foo.serviceTwo();foo.serviceThree();技术2 –用默认值替换用另一个对象导出对象
至此,您可能会怀疑,鉴于事实
module.exports仅仅是一个对象,它公开了模块的公共部分,因此我们可能会定义自己的对象,然后用我们自己的对象替换默认
module.exports对象。例如:
//module foo.jsvar service = { serviceOne: function(){ }, serviceTwo: function(){ }, serviceThree = function(){ }};module.exports = service;最后一个示例中的代码的行为与上一个示例中的代码完全相同,只是这次我们显式创建了导出的对象,而不是使用Node默认提供的对象。
技术3 –用构造函数替换默认导出对象
到目前为止,在示例中,我们始终使用对象的实例作为公开目标。但是,在某些情况下,允许用户根据需要创建任意数量的给定类型的实例似乎更为方便。没有什么可以阻止我们
module.exports用其他类型的对象(例如构造函数)替换该对象。在下面的示例中,我们提供了一个构造函数,用户可以使用该构造函数来创建该
Foo类型的许多实例。
//module Foo.jsfunction Foo(name){ this.name = name;}Foo.prototype.serviceOne = function(){ };Foo.prototype.serviceTwo = function(){ };Foo.prototype.serviceThree = function(){ };module.exports = Foo;该模块的用户可以简单地执行以下操作:
//module bar.jsvar Foo = require('./Foo');var foo = new Foo('Obi-wan');foo.serviceOne();foo.serviceTwo();foo.serviceThree();技术4 –用默认的旧函数替换默认导出对象
现在可以轻松想象,如果我们可以使用构造函数,那么我们也可以将任何其他普通的旧Javascript函数用作公开的目标
module.exports。如以下示例所示,其中我们的导出功能允许该模块的用户访问其他几个封装的服务对象之一。
//foo.jsvar serviceA = {};serviceA.serviceOne = function(){ };serviceA.serviceTwo = function(){ };serviceA.serviceThree = function(){ };var serviceB = {};serviceB.serviceOne = function(){ };serviceB.serviceTwo = function(){ };serviceB.serviceThree = function(){ };module.exports = function(name){ switch(name){ case 'A': return serviceA; case 'B': return serviceB; default: throw new Error('Unknown service name: ' + name); }};现在,导入此模块的用户将收到对上面声明的匿名函数的引用,然后她可以简单地调用该函数来访问我们封装的对象之一。例如:
//module bar.jsvar foo = require('./foo');var obj = foo('A');obj.serviceOne();obj.serviceTwo();obj.serviceThree();通常,许多程序员会立即调用require返回的函数,而不是先将其分配给引用。例如:
//module bar.jsvar foo = require('./foo')('A');foo.serviceOne();foo.serviceTwo();foo.serviceThree();因此,总的来说,它很简单,如下所示:公开的所有内容都是
module.exports调用时得到的
require。使用不同的技术,我们可以公开对象,构造函数,属性等。
基于所有这些示例,我说在我的代码中使用模块模式对我来说没有任何意义。
使用模块模式和Node.js模块
但是,如果您要创建要在Node.js和浏览器中使用的库,则使用这两种模式都是有意义的。但是,这在您的问题中并不明显。但是,如果是这样,您可以将这两个想法结合在一起。
例如,执行以下操作:
var TestModule;(function (TestModule) { var counter = 0; TestModule.incrementCounter = function(){ return counter++; }; TestModule.resetCounter = function(){ console.log('Last Counter Value before RESET :' + counter); counter = 0; }; return TestModule;})(typeof module === 'undefined' ? (TestModule || (TestModule = {})) : module.exports);在Node.js中运行时,则在IIFE中
TestModule与
module.exports对象相对应;在浏览器中运行时,则
TestModule表示名称空间。
因此,如果您在Node中运行,则可以执行以下操作:
var testModule = require('./testModule');testModule.incrementCounter();而且,如果您正在浏览器中运行,那么在加载此脚本之后,您可以直接访问名称空间TestModule。
TestModule.incrementCounter();



