mko*_*yak 86 javascript singleton design-patterns node.js
最近,我碰上了这篇文章如何写在Node.js的一个单 我知道require 各州的文件:
模块在第一次加载后进行缓存.多次调用
require('foo')可能不会导致模块代码多次执行.
因此,似乎每个必需的模块都可以很容易地用作单例,而不需要单独的样板代码.
题:
上面的文章是否提供了关于创建单例的解决方案?
小智 134
所有上述内容都过于复杂.有一种思想流派认为设计模式显示出实际语言的缺陷.
基于原型的OOP(无类别)语言根本不需要单例模式.您只需动态创建一个(吨)对象,然后使用它.
至于节点中的模块,是的,默认情况下它们是缓存的,但是如果你想热插入模块更改,可以调整它.
但是,是的,如果你想全部使用共享对象,将它放入模块导出就可以了.只是不要将它与"单例模式"复杂化,在JavaScript中不需要它.
Kar*_*son 41
https://nodejs.org/api/modules.html#modules_caching
(v 6.3.1)
高速缓存
模块在第一次加载后进行缓存.这意味着(除其他外)每次调用require('foo')将获得完全相同的返回对象,如果它将解析为同一个文件.
多次调用require('foo')可能不会导致模块代码多次执行.这是一个重要的特征.有了它,就可以返回"部分完成"的对象,从而允许加载传递依赖,即使它们会导致循环.
如果要让模块多次执行代码,则导出一个函数,然后调用该函数.
模块缓存警告
模块根据其解析的文件名进行缓存.由于模块可能会根据调用模块的位置(从node_modules文件夹加载)解析为不同的文件名,因此不能保证require('foo')将始终返回完全相同的对象,如果它将解析为不同的文件.
此外,在不区分大小写的文件系统或操作系统上,不同的已解析文件名可以指向同一文件,但缓存仍将它们视为不同的模块,并将多次重新加载该文件.例如,require('./ foo')和require('./ FOO')返回两个不同的对象,无论./foo和./FOO是否是同一个文件.
所以简单来说.
如果你想要一个单身人士; 导出一个对象.
如果你不想要一个单身人士; 导出一个函数(并在该函数中执行东西/返回东西/任何东西).
非常清楚,如果你这样做,它应该工作,看看/sf/answers/2362269241/(Allen Luce的回答).它在代码中解释了当缓存由于不同解析的文件名而失败时会发生什么.但如果你总是解析相同的文件名,它应该工作.
All*_*uce 23
号 当节点的缓存模块发生故障,该单例模式失败.我修改了示例以在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());
Run Code Online (Sandbox Code Playgroud)
这给出了作者预期的输出:
{ '1': 'test', '2': 'test2' } { '1': 'test', '2': 'test2' }
Run Code Online (Sandbox Code Playgroud)
但是一个小修改就会破坏缓存.在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());
Run Code Online (Sandbox Code Playgroud)
或者,在Linux上:
% ln singleton.js singleton2.js
Run Code Online (Sandbox Code Playgroud)
然后将sg2require行更改为:
var sg2 = require("./singleton2.js");
Run Code Online (Sandbox Code Playgroud)
而咣,单身被击败:
{ '1': 'test' } { '2': 'test2' }
Run Code Online (Sandbox Code Playgroud)
我不知道一种可以接受的解决方法.如果你真的觉得需要制作类似单身的东西并且可以污染全局命名空间(以及可能导致的许多问题),你可以将作者getInstance()和exports行改为:
singleton.getInstance = function(){
if(global.singleton_instance === undefined)
global.singleton_instance = new singleton();
return global.singleton_instance;
}
module.exports = singleton.getInstance();
Run Code Online (Sandbox Code Playgroud)
也就是说,我从来没有遇到生产系统的情况,我需要做这样的事情.我也从未觉得需要在Javascript中使用单例模式.
mik*_*ike 20
进一步了解模块文档中的模块缓存注意事项:
模块根据其解析的文件名进行缓存.由于模块可能会根据调用模块的位置(从node_modules文件夹加载)解析为不同的文件名,因此不能保证 require('foo')将始终返回完全相同的对象,如果它将解析为不同的文件.
因此,根据您在需要模块时的位置,可以获得模块的不同实例.
听起来像模块不是创建单例的简单解决方案.
编辑:或许他们是.像@mkoryak一样,我无法想出一个单个文件可能解析为不同文件名的情况(不使用符号链接).但是(如@JohnnyHK评论),不同node_modules目录中的文件的多个副本将分别加载和存储.
Pau*_*ong 18
node.js中的单例(或者在浏览器JS中,就此而言)是完全没必要的.
由于模块是缓存和有状态的,因此您提供的链接上给出的示例可以更轻松地重写:
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
Run Code Online (Sandbox Code Playgroud)
在js中你不需要任何特殊的单例,文章中的代码也可以是:
var socketList = {};
module.exports = {
add: function() {
},
...
};
Run Code Online (Sandbox Code Playgroud)
在node.js之外(例如,在浏览器js中),您需要手动添加包装函数(它在node.js中自动完成):
var singleton = function() {
var socketList = {};
return {
add: function() {},
...
};
}();
Run Code Online (Sandbox Code Playgroud)
单例在 JS 中很好,只是不需要那么冗长。
在 node 中,如果您需要单例,例如在服务器层的各种文件中使用相同的 ORM/DB 实例,您可以将引用填充到全局变量中。
只需编写一个模块来创建全局变量(如果它不存在),然后返回对该变量的引用。
@allen-luce 在这里复制了他的脚注代码示例:
singleton.getInstance = function(){
if(global.singleton_instance === undefined)
global.singleton_instance = new singleton();
return global.singleton_instance;
};
module.exports = singleton.getInstance();
Run Code Online (Sandbox Code Playgroud)
但重要的是要注意使用new关键字不是必需的。任何旧的对象、函数、iife 等都可以工作——这里没有发生 OOP 巫毒教。
如果您在返回对它的引用的函数中关闭某个 obj 并将该函数设为全局函数,则加分项 - 那么即使重新分配全局变量也不会破坏已经从它创建的实例 - 尽管这很有用。
这里唯一使用ES6类的答案
// SummaryModule.js
class Summary {
init(summary) {
this.summary = summary
}
anotherMethod() {
// do something
}
}
module.exports = new Summary()
Run Code Online (Sandbox Code Playgroud)
要求这个单身人士:
const summary = require('./SummaryModule')
summary.init(true)
summary.anotherMethod()
Run Code Online (Sandbox Code Playgroud)
这里唯一的问题是你不能将params传递给类构造函数,但可以通过手动调用init方法来规避.