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
方法来避免。