nodejs中的单例模式 – 是否需要?

我最近遇到了关于如何在Node.js中编写单例的文章。 我知道require的文档说:

模块在第一次加载后被caching。 多次调用require('foo')可能不会导致模块代码被多次执行。

所以看起来,每个需要的模块都可以很容易地使用一个单例,而不需要单独的样板代码。

题:

上面的文章是否提供了创build单例的解决scheme?

这基本上与nodejscaching有关。 干净利落。

https://nodejs.org/api/modules.html#modules_caching

(v 6.3.1)

高速caching

模块在第一次加载后被caching。 这意味着(除其他外)每个调用require('foo')将返回完全相同的对象,如果它将parsing为相同的文件。

多次调用require('foo')可能不会导致模块代码被多次执行。 这是一个重要的function。 有了它,“部分完成”的对象可以被返回,从而允许传递依赖被加载,即使它们会导致循环。

如果你想要一个模块多次执行代码,那么导出一个函数,然后调用这个函数。

模块caching警告

模块基于他们parsing的文件名被caching。 由于模块可能会根据调用模块的位置(从node_modules文件夹加载)parsing为不同的文件名,因此不能保证require('foo')将始终返回完全相同的对象,如果parsing为不同的文件。

此外,在不区分大小写的文件系统或操作系统,不同的parsing文件名可以指向同一个文件,但caching仍然将它们视为不同的模块,并将多次重新加载文件。 例如,require('./ foo')和require('./ FOO')返回两个不同的对象,不pipe./foo和./FOO是否是同一个文件。

所以简单来说

如果你想要一个单身人士; 导出一个对象

如果你不想要一个单身人士; 导出一个函数 (并做东西/返回的东西/无论在该函数)。

以上所有都过于复杂。 有一个思想stream派说devise模式显示出实际语言的不足。

基于原型的OOP(无类)语言根本不需要单例模式。 您只需简单地创build一个(吨)对象然后使用它。

至于节点中的模块,是的,默认情况下它们被caching,但是可以调整,例如,如果你想要热载入模块的变化。

但是,是的,如果你想使用共享对象,把它放在一个模块出口是好的。 只是不要用“单例模式”使其复杂化,在JavaScript中不需要它。

node.js中的单例(或者在浏览器JS中,就此而言)是完全没有必要的。

由于模块caching和有状态,您提供的链接上给出的例子可以很容易地重写得更简单:

 var socketList = {}; exports.add = function (userId, socket) { if (!socketList[userId]) { socketList[userId] = socket; } }; exports.remove = function (userId) { delete socketList[userId]; }; exports.getSocketList = function () { return socketList; }; // or // exports.socketList = socketList 

在“模块”文档中的“ 模块caching警告”中进一步了解一下:

模块基于他们parsing的文件名被caching。 由于模块可能会根据调用模块的位置(从node_modules文件夹加载)parsing为不同的文件名, 因此不能保证 require('foo')将始终返回完全相同的对象,如果parsing为不同的文件。

所以,当你需要一个模块的时候,根据你的位置,可以得到一个不同的模块实例。

听起来像模块不是一个简单的解决scheme来创build单身人士。

编辑:或者他们 。 像@ mkoryak,我不能想出一个单一的文件可能会parsing为不同的文件名(不使用符号链接)的情况。 但是(如@JohnnyHK注释),在不同node_modules目录中的文件的多个副本将分别加载和存储。

不。当Node的模块caching失败时,该单例模式失败。 我修改了这个例子来在OSX上有意义的运行:

 var sg = require("./singleton.js"); var sg2 = require("./singleton.js"); sg.add(1, "test"); sg2.add(2, "test2"); console.log(sg.getSocketList(), sg2.getSocketList()); 

这给作者预期的输出:

 { '1': 'test', '2': 'test2' } { '1': 'test', '2': 'test2' } 

但是一个小小的修改就会导致caching失败。 在OSX上,执行以下操作:

 var sg = require("./singleton.js"); var sg2 = require("./SINGLETON.js"); sg.add(1, "test"); sg2.add(2, "test2"); console.log(sg.getSocketList(), sg2.getSocketList()); 

或者,在Linux上:

 % ln singleton.js singleton2.js 

然后将sg2 require行更改为:

 var sg2 = require("./singleton2.js"); 

巴姆 ,这个单身人士被击败了:

 { '1': 'test' } { '2': 'test2' } 

我不知道有一个可以接受的方法来解决这个问题。 如果你真的觉得需要做一些类似于单例的事情,并且可以污染全局命名空间(以及可能导致的许多问题),那么可以将作者的getInstance()exports行更改为:

 singleton.getInstance = function(){ if(global.singleton_instance === undefined) global.singleton_instance = new singleton(); return global.singleton_instance; } module.exports = singleton.getInstance(); 

也就是说,我从来没有遇到生产系统的情况,我需要做这样的事情。 我从来没有觉得需要在Javascript中使用单例模式。

你不需要任何特殊的东西在js中做单身,文章中的代码也可以是:

 var socketList = {}; module.exports = { add: function() { }, ... }; 

在node.js之外(例如,在浏览器js中),您需要手动添加包装函数(它在node.js中自动完成):

 var singleton = function() { var socketList = {}; return { add: function() {}, ... }; }(); 

单身人士在JS中很好,他们不需要那么冗长。

在节点中,如果你需要一个单例,例如在你的服务器层的不同文件中使用相同的ORM /数据库实例,你可以将引用填充到一个全局variables中。

只要写一个创build全局variables的模块(如果它不存在),然后返回一个引用。

@ allen-luce把他的脚注代码例子复制到了这里:

 singleton.getInstance = function(){ if(global.singleton_instance === undefined) global.singleton_instance = new singleton(); return global.singleton_instance; }; module.exports = singleton.getInstance(); 

但重要的是要注意,使用new关键字不是必需的。 任何旧的对象,function,生活等都将起作用 – 这里没有OOP巫术发生。

如果你在一个返回引用的函数中closures某个obj,并且使该函数成为全局函数,那么即使重新分配全局variables也不会破坏已经创build的实例 – 虽然这是有用的。

保持简单。

foo.js

 function foo() { bar: { doSomething: function(arg, callback) { return callback('Echo ' + arg); }; } return bar; }; module.exports = foo(); 

然后就是

 var foo = require(__dirname + 'foo'); foo.doSomething('Hello', function(result){ console.log(result); }); 

这里唯一的答案是使用ES6类

 // SummaryModule.js class Summary { init(summary) { this.summary = summary } anotherMethod() { // do something } } module.exports = new Summary() 

要求这个单身人士:

 const summary = require('./SummaryModule') summary.init(true) summary.anotherMethod() 

这里唯一的问题是你不能将params传递给类的构造函数,但是可以通过手动调用init方法来避免。