nodejs 中的单例模式 - 是否需要?
以上这些都过于复杂了。有一种学派认为设计模式显示了实际语言的缺陷。
具有基于原型的 OOP(无类)的语言根本不需要单例模式。您只需动态创建一个(吨)对象,然后使用它。
至于节点中的module,是的,默认情况下它们是缓存的,但是如果您想要热加载module更改,则可以对其进行调整。
但是是的,如果你想全部使用共享对象,把它放在一个module导出中就可以了。只是不要将它与“单例模式”复杂化,在 JavaScript 中不需要它。
这基本上与 nodejs 缓存有关。干净利落。
https://nodejs.org/api/modules.html#modules_caching
(v 6.3.1)
缓存
module在第一次加载后被缓存。这意味着(除其他外)每次调用 require('foo') 都会返回完全相同的对象,如果它解析为相同的文件。
多次调用 require('foo') 可能不会导致module代码被多次执行。这是一个重要的特征。有了它,可以返回“部分完成”的对象,从而允许加载传递依赖项,即使它们会导致循环。
如果你想让一个module多次执行代码,那么导出一个函数,然后调用那个函数。
module缓存注意事项
module根据其解析的文件名进行缓存。由于module可能会根据调用module的位置(从 node_modules 文件夹加载)解析为不同的文件名,因此不保证 require('foo') 将始终返回完全相同的对象,如果它解析为不同的文件.
此外,在不区分大小写的文件系统或操作系统上,不同的解析文件名可以指向同一个文件,但缓存仍会将它们视为不同的module,并会多次重新加载文件。例如, require('./foo') 和 require('./FOO') 返回两个不同的对象,无论 ./foo 和 ./FOO 是否是同一个文件。
所以简单来说。
如果你想要一个单身人士;导出一个对象。
如果您不想要单身人士;导出一个函数(并在该函数中做东西/返回东西/任何东西)。
非常清楚,如果您正确执行此操作,它应该可以工作,请查看https://stackoverflow.com/a/33746703/1137669(Allen Luce 的回答)。它在代码中解释了由于不同解析的文件名导致缓存失败时会发生什么。但是如果你总是解析为相同的文件名,它应该可以工作。
2016 年更新
在 node.js 中用 es6 符号创建一个真正的单例 另一个解决方案:在这个链接中
2020 年更新
这个答案是指CommonJS(Node.js 自己的导入/导出module的方式)。Node.js 很可能会切换到ECMAScript module:https : //nodejs.org/api/esm.html (如果您不知道,ECMAScript 是 JavaScript 的真实名称)
迁移到 ECMAScript 时,请暂时阅读以下内容:https : //nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards
号 当节点的缓存module发生故障,该单例模式失败。我修改了示例以在 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' }
但是一个小的修改会破坏缓存。在 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");
和bam,单身人士被击败:
{ '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 中使用单例模式。
进一步查看module文档中的module缓存警告:
module根据其解析的文件名进行缓存。由于module可能会根据调用module的位置(从 node_modules 文件夹加载)解析为不同的文件名,因此不保证require('foo') 将始终返回完全相同的对象,如果它解析为不同的文件.
因此,根据您在需要module时所处的位置,可能会获得该module的不同实例。
听起来像module不是创建单例的简单解决方案。
编辑:或者也许他们是. 像@mkoryak 一样,我无法想出单个文件可能解析为不同文件名的情况(不使用符号链接)。但是(正如@JohnnyHK 评论的那样),不同node_modules
目录中文件的多个副本将分别加载和存储。
node.js(或浏览器 JS,就此而言)中的单例是完全没有必要的。
由于module是缓存和有状态的,您提供的链接中给出的示例可以轻松地更简单地重写:
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