Answers:
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是否是同一文件。
简单来说。
如果要单身人士;导出对象。
如果您不想要单身人士;导出函数(并在该函数中执行填充/返回填充/任何操作)。
要非常清楚,如果正确执行此操作应该可以正常工作,请查看https://stackoverflow.com/a/33746703/1137669(Allen Luce的回答)。它在代码中解释了由于解析的文件名不同而导致缓存失败时会发生什么。但是,如果您始终解析为相同的文件名,则应该可以使用。
使用es6符号在node.js中创建真正的单例 另一个解决方案:在此链接中
此答案涉及CommonJS(Node.js自己导入/导出模块的方式)。Node.js很可能会切换到ECMAScript模块:https : //nodejs.org/api/esm.html (如果您不知道,ECMAScript是JavaScript的真实名称)
迁移到ECMAScript时,请先阅读以下内容:https : //nodejs.org/api/esm.html#esm_writing_dual_packages_while_avoiding_or_minimizing_hazards
以上所有内容都过于复杂。有一种流派认为设计模式显示出实际语言的不足。
具有基于原型的OOP(无类)的语言根本不需要单例模式。您只需动态创建一个(吨)对象,然后使用它。
对于节点中的模块,是的,默认情况下会对其进行缓存,但是例如,如果您想热加载模块更改,则可以对其进行调整。
但是,是的,如果您想使用共享对象,请将其放在模块导出中就可以了。只是不要使其与“单一模式”复杂化,而在JavaScript中则不需要它。
There is a school of thought which says design patterns are showing deficiencies of actual language.
否。 当Node的模块缓存失败时,该单例模式也会失败。我修改了示例以使其在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");
而咣,单身被击败:
{ '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中使用单例模式。
再看一下Modules文档中的Module Caching注意事项:
根据解析的文件名缓存模块。由于模块可能会根据调用模块的位置(从node_modules文件夹加载)而解析为不同的文件名,因此,不能保证 require('foo')始终会返回完全相同的对象(如果它将解析为不同的文件) 。
因此,根据需要模块的位置,可以获取模块的其他实例。
听起来像模块不是创建单例的简单解决方案。
编辑:也许他们是。像@mkoryak一样,我无法解决单个文件可能解析为不同文件名(不使用符号链接)的情况。但是(如@JohnnyHK的评论),文件在不同node_modules
目录中的多个副本将分别加载和存储。
node_modules
,每个模块都依赖于同一模块,但是node_modules
在两个不同模块的每个子目录下都有该依赖模块的单独副本。
require('./db')
在两个单独的文件中,该db
模块的代码执行两次
require('../lib/myModule.js');
在一个文件中调用了另一个文件,require('../lib/mymodule.js');
但没有传递相同的对象。
这样就完全不需要在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
may not
适用于当你npm link
在开发过程中的其他模块。因此,在使用依赖于单个实例的模块(例如eventBus)时要小心。
这里唯一使用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()
这里唯一的问题是您不能将参数传递给类构造函数,但是可以通过手动调用init
方法来避免这种情况。
summary
在另一个类中使用同一实例,而无需再次对其进行初始化?
您不需要任何特殊操作即可在js中执行单例操作,本文中的代码也可以是:
var socketList = {};
module.exports = {
add: function() {
},
...
};
在node.js之外(例如,在浏览器js中),您需要手动添加包装函数(在node.js中自动完成):
var singleton = function() {
var socketList = {};
return {
add: function() {},
...
};
}();
Singleton在JS中很好,只是不需要那么冗长。
在节点中,如果您需要单例(例如,要在服务器层的各个文件中使用相同的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();
但请注意,使用new
关键字不是必需的。任何旧的对象,功能,示例等都可以使用-这里没有发生OOP伏都教。
如果您在函数中关闭某个obj并返回对该函数的引用并使其成为全局函数,则可以加分-即便重新分配全局变量也不会破坏已经从其创建的实例-尽管这样做很有用。
module.exports = new Foo()
是因为module.exports不会再次执行,除非您做的非常愚蠢
保持简单。
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); });