在JavaScript中实现单例模式的最简单/最干净的方法是什么?
在JavaScript中实现单例模式的最简单/最干净的方法是什么?
Answers:
我认为最简单的方法是声明一个简单的对象文字:
var myInstance = {
method1: function () {
// ...
},
method2: function () {
// ...
}
};
如果您希望单例实例上有私人成员,则可以执行以下操作:
var myInstance = (function() {
var privateVar = '';
function privateMethod () {
// ...
}
return { // public interface
publicMethod1: function () {
// all private members are accessible here
},
publicMethod2: function () {
}
};
})();
这就是所谓的模块模式,它基本上可以让你来封装对象私有成员,通过采取利用的优势关闭。
更新:我想补充一点,如果要防止修改单例对象,可以使用ES5 方法冻结它Object.freeze
。
这将使对象不可变,从而防止对其结构和值进行任何修改。
另外,我想提到的是,如果您使用的是ES6,则可以很容易地使用ES模块表示一个单例,甚至可以通过在模块范围内声明变量来保持私有状态:
// my-singleton.js
const somePrivateState = []
function privateFn () {
// ...
}
export default {
method1() {
// ...
},
method2() {
// ...
}
}
然后,您可以简单地导入单例对象以使用它:
import myInstance from './my-singleton.js'
// ...
publicMethod1
称呼publicMethod2
?
getInstance
方法和私有构造函数-但是IMO,这是构建单例对象的最“简单”方式在Javascript中,最后达到了相同的目的-一个无法再次初始化的对象(没有构造函数,它只是一个对象)-。关于链接的代码,它存在一些问题,交换a
和b
变量声明以及test a === window
。干杯。
我认为最干净的方法是:
var SingletonFactory = (function(){
function SingletonClass() {
//do stuff
}
var instance;
return {
getInstance: function(){
if (instance == null) {
instance = new SingletonClass();
// Hide the constructor so the returned object can't be new'd...
instance.constructor = null;
}
return instance;
}
};
})();
之后,您可以将函数调用为
var test = SingletonFactory.getInstance();
delete instance.constructor
以下方法再次读取原始构造函数:x = SingletonClass.getInstance();delete x.constructor;new x.constructor;
我不确定我是否同意将模块模式用作单例模式的替代品。我经常看到在完全不需要的地方使用和滥用单例,并且我敢肯定,模块模式会填补许多程序员在其他情况下会使用单例的空白,但是模块模式不是单例。
var foo = (function () {
"use strict";
function aPrivateFunction() {}
return { aPublicFunction: function () {...}, ... };
}());
在模块模式中初始化的所有事情都会在以下情况发生 Foo
声明。此外,模块模式可用于初始化构造函数,然后可以多次实例化该构造函数。尽管模块模式是许多作业的正确工具,但它并不等同于单例。
var Foo = function () {
"use strict";
if (Foo._instance) {
//this allows the constructor to be called multiple times
//and refer to the same instance. Another option is to
//throw an error.
return Foo._instance;
}
Foo._instance = this;
//Foo initialization code
};
Foo.getInstance = function () {
"use strict";
return Foo._instance || new Foo();
}
长格式,使用模块模式
var Foo = (function () {
"use strict";
var instance; //prevent modification of "instance" variable
function Singleton() {
if (instance) {
return instance;
}
instance = this;
//Singleton initialization code
}
//instance accessor
Singleton.getInstance = function () {
return instance || new Singleton();
}
return Singleton;
}());
在我提供的两个版本的Singleton模式中,构造函数本身都可以用作访问器:
var a,
b;
a = new Foo(); //constructor initialization happens here
b = new Foo();
console.log(a === b); //true
如果您不习惯以这种方式使用构造函数,则可以在if (instance)
语句中引发错误,并坚持使用长格式:
var a,
b;
a = Foo.getInstance(); //constructor initialization happens here
b = Foo.getInstance();
console.log(a === b); //true
我还应该提到单例模式非常适合隐式构造函数功能模式:
function Foo() {
if (Foo._instance) {
return Foo._instance;
}
//if the function wasn't called as a constructor,
//call it as a constructor and return the result
if (!(this instanceof Foo)) {
return new Foo();
}
Foo._instance = this;
}
var f = new Foo(); //calls Foo as a constructor
-or-
var f = Foo(); //also calls Foo as a constructor
var singleton = {}
不符合该定义。
var singleton = {}
这是在Javascript中实现单例的方式。
在es6
:
class Singleton {
constructor () {
if (!Singleton.instance) {
Singleton.instance = this
}
// Initialize object
return Singleton.instance
}
// Properties & Methods
}
const instance = new Singleton()
Object.freeze(instance)
export default instance
instance
字段,那么冻结将是有意义的。由于目前(instance
设置为this
),此类可能还包含其他字段,因此冻结没有意义。
以下在节点v6中起作用
class Foo {
constructor(msg) {
if (Foo.singleton) {
return Foo.singleton;
}
this.msg = msg;
Foo.singleton = this;
return Foo.singleton;
}
}
我们测试:
const f = new Foo('blah');
const d = new Foo('nope');
console.log(f); // => Foo { msg: 'blah' }
console.log(d); // => Foo { msg: 'blah' }
在ES6中,正确的方法是:
class MyClass {
constructor() {
if (MyClass._instance) {
throw new Error("Singleton classes can't be instantiated more than once.")
}
MyClass._instance = this;
// ... your rest of the constructor code goes after this
}
}
var instanceOne = new MyClass() // Executes succesfully
var instanceTwo = new MyClass() // Throws error
或者,如果您不希望在创建第二个实例时引发错误,则可以只返回最后一个实例,如下所示:
class MyClass {
constructor() {
if (MyClass._instance) {
return MyClass._instance
}
MyClass._instance = this;
// ... your rest of the constructor code goes after this
}
}
var instanceOne = new MyClass()
var instanceTwo = new MyClass()
console.log(instanceOne === instanceTwo) // logs "true"
instance
和中没有技术差异_instance
。这只是编程语言中的命名约定,我们用下划线命名私有变量。我怀疑您的代码无法正常工作的原因是您使用的this.instance
不是MyClass.instance
剥皮猫的方法不止一种:)根据您的口味或特定需求,您可以应用任何建议的解决方案。我个人尽可能地寻求CMS的第一个解决方案(当您不需要隐私时)。由于问题是关于最简单,最干净的,因此是赢家。甚至:
var myInstance = {}; // done!
这(从我的博客引用)...
var SingletonClass = new function() {
this.myFunction() {
//do stuff
}
this.instance = 1;
}
没什么意义(我的博客示例也没有),因为它不需要任何私有变量,因此与以下内容几乎相同:
var SingletonClass = {
myFunction: function () {
//do stuff
},
instance: 1
}
this.f(){}
我不赞成我的答案,请参阅另一个。
通常不是单例模式的模块模式(请参阅CMS的答案)就足够了。但是,单例的特征之一是其初始化被延迟到需要对象为止。模块模式缺少此功能。
我的主张(CoffeeScript):
window.singleton = (initializer) ->
instance = undefined
() ->
return instance unless instance is undefined
instance = initializer()
用JavaScript编译为此:
window.singleton = function(initializer) {
var instance;
instance = void 0;
return function() {
if (instance !== void 0) {
return instance;
}
return instance = initializer();
};
};
然后,我可以执行以下操作:
window.iAmSingleton = singleton(function() {
/* This function should create and initialize singleton. */
alert("creating");
return {property1: 'value1', property2: 'value2'};
});
alert(window.iAmSingleton().property2); // "creating" will pop up; then "value2" will pop up
alert(window.iAmSingleton().property2); // "value2" will pop up but "creating" will not
window.iAmSingleton().property2 = 'new value';
alert(window.iAmSingleton().property2); // "new value" will pop up
由于JavaScript具有非阻塞性,因此JavaScript中的Singleton确实很难使用。全局变量也将在整个应用程序中为您提供一个实例,而无需所有这些回调,模块模式将内部构件轻轻地隐藏在接口后面。请参阅@CMS答案。
但是,由于您想要一个单身人士...
var singleton = function(initializer) {
var state = 'initial';
var instance;
var queue = [];
var instanceReady = function(createdInstance) {
state = 'ready';
instance = createdInstance;
while (callback = queue.shift()) {
callback(instance);
}
};
return function(callback) {
if (state === 'initial') {
state = 'waiting';
queue.push(callback);
initializer(instanceReady);
} else if (state === 'waiting') {
queue.push(callback);
} else {
callback(instance);
}
};
};
用法:
var singletonInitializer = function(instanceReady) {
var preparedObject = {property: 'value'};
// calling instanceReady notifies singleton that instance is ready to use
instanceReady(preparedObject);
}
var s = singleton(singletonInitializer);
// get instance and use it
s(function(instance) {
instance.doSomething();
});
单例在整个应用程序中为您提供的不仅仅是一个实例:它们的初始化被延迟到首次使用。当您处理初始化成本很高的对象时,这确实是一件大事。昂贵通常意味着I / O,而在JavaScript中I / O总是意味着回调。
不要相信会给您界面的答案instance = singleton.getInstance()
,他们都会错过重点。
如果实例准备就绪时它们不接受回调来运行,那么当初始化器执行I / O时它们将不起作用。
是的,回调总是比立即返回对象实例的函数调用难看。再说一遍:执行I / O时,必须执行回调。如果您不想执行任何I / O,则实例化在程序启动时就足够便宜了。
var simpleInitializer = function(instanceReady) {
console.log("Initializer started");
instanceReady({property: "initial value"});
}
var simple = singleton(simpleInitializer);
console.log("Tests started. Singleton instance should not be initalized yet.");
simple(function(inst) {
console.log("Access 1");
console.log("Current property value: " + inst.property);
console.log("Let's reassign this property");
inst.property = "new value";
});
simple(function(inst) {
console.log("Access 2");
console.log("Current property value: " + inst.property);
});
在此示例中,setTimeout
伪造了一些昂贵的I / O操作。这说明了为什么JavaScript中的单例确实需要回调。
var heavyInitializer = function(instanceReady) {
console.log("Initializer started");
var onTimeout = function() {
console.log("Initializer did his heavy work");
instanceReady({property: "initial value"});
};
setTimeout(onTimeout, 500);
};
var heavy = singleton(heavyInitializer);
console.log("In this example we will be trying");
console.log("to access singleton twice before it finishes initialization.");
heavy(function(inst) {
console.log("Access 1");
console.log("Current property value: " + inst.property);
console.log("Let's reassign this property");
inst.property = "new value";
});
heavy(function(inst) {
console.log("Access 2. You can see callbacks order is preserved.");
console.log("Current property value: " + inst.property);
});
console.log("We made it to the end of the file. Instance is not ready yet.");
我是从JavaScript模式中获得此示例的,该代码使用Stoyan Stefanov的书编写了使用编码和设计模式构建更好的应用程序,如果您需要一些简单的实现类(例如singltone对象),则可以使用以下即时函数:
var ClassName;
(function() {
var instance;
ClassName = function ClassName() {
//If private instance variable already initialized return reference
if(instance) {
return instance;
}
//If instance does not created save pointer of original reference
//to private instance variable.
instance = this;
//All constructor initialization will be here
// i.e.:
this.someProperty = 0;
this.someMethod = function() {
//Some action here
};
};
}());
您可以通过以下测试用例来检查此示例:
//Extending defined class like Singltone object using new prototype property
ClassName.prototype.nothing = true;
var obj_1 = new ClassName();
//Extending defined class like Singltone object using new prototype property
ClassName.prototype.everything = true;
var obj_2 = new ClassName();
//Testing does this two object pointing to same instance
console.log(obj_1 === obj_2); //Result is true, it points to same instance object
//All prototype properites work
//no matter when they were defined
console.log(obj_1.nothing && obj_1.everything
&& obj_2.nothing && obj_2.everything); //Result true
//Values of properties which is defined inside of constructor
console.log(obj_1.someProperty);// output 0
console.log(obj_2.someProperty);// output 0
//Changing property value
obj_1.someProperty = 1;
console.log(obj_1.someProperty);// output 1
console.log(obj_2.someProperty);// output 1
console.log(obj_1.constructor === ClassName); //Output true
这种方法通过了所有测试用例,而使用原型扩展时私有静态实现将失败(可以固定但并不简单),并且由于实例公开给公众,因此不建议使用公共静态实现。
我想我已经找到了使用JavaScript编程的最简洁方法,但是您需要一些想象力。我从“ javascript的好部分”一书中的一种工作技巧中得到了这个想法。
除了使用new关键字,您还可以创建一个这样的类:
function Class()
{
var obj = {}; // Could also be used for inheritence if you don't start with an empty object.
var privateVar;
obj.publicVar;
obj.publicMethod= publicMethod;
function publicMethod(){}
function privateMethod(){}
return obj;
}
您可以通过以下方式实例化上述对象:
var objInst = Class(); // !!! NO NEW KEYWORD
现在考虑到这种工作方法,您可以创建一个单例,如下所示:
ClassSingleton = function()
{
var instance= null;
function Class() // This is the class like the above one
{
var obj = {};
return obj;
}
function getInstance()
{
if( !instance )
instance = Class(); // Again no new keyword;
return instance;
}
return { getInstance : getInstance };
}();
现在您可以通过调用获取实例
var obj = ClassSingleton.getInstance();
我认为这是最整洁的方式,因为甚至无法访问完整的“类”。
@CMS和@zzzzBov都给出了很好的答案,但仅仅是基于我从单例模式很常见的PHP / Zend Framework转移到沉重的node.js开发中来添加自己的解释。
以下注释文档代码基于以下要求:
我的代码与@zzzzBov的代码非常相似,除了我向构造函数添加了原型链,并添加了更多注释,这些注释应该有助于那些来自PHP或类似语言的人将传统的OOP转换为Javascript的原型性质。它可能不是“最简单的”,但我认为它是最适当的。
// declare 'Singleton' as the returned value of a self-executing anonymous function
var Singleton = (function () {
"use strict";
// 'instance' and 'constructor' should not be availble in a "public" scope
// here they are "private", thus available only within
// the scope of the self-executing anonymous function
var _instance=null;
var _constructor = function (name) {
this.name = name || 'default';
}
// prototypes will be "public" methods available from the instance
_constructor.prototype.getName = function () {
return this.name;
}
// using the module pattern, return a static object
// which essentially is a list of "public static" methods
return {
// because getInstance is defined within the same scope
// it can access the "private" 'instance' and 'constructor' vars
getInstance:function (name) {
if (!_instance) {
console.log('creating'); // this should only happen once
_instance = new _constructor(name);
}
console.log('returning');
return _instance;
}
}
})(); // self execute
// ensure 'instance' and 'constructor' are unavailable
// outside the scope in which they were defined
// thus making them "private" and not "public"
console.log(typeof _instance); // undefined
console.log(typeof _constructor); // undefined
// assign instance to two different variables
var a = Singleton.getInstance('first');
var b = Singleton.getInstance('second'); // passing a name here does nothing because the single instance was already instantiated
// ensure 'a' and 'b' are truly equal
console.log(a === b); // true
console.log(a.getName()); // "first"
console.log(b.getName()); // also returns "first" because it's the same instance as 'a'
请注意,从技术上讲,自执行匿名函数本身就是Singleton,如@CMS提供的代码中很好地演示的那样。这里唯一要注意的是,当构造函数本身是匿名的时,不可能修改构造函数的原型链。
请记住,对于Javascript,“公共”和“私有”的概念并不像在PHP或Java中那样适用。但是,我们通过利用Javascript的功能范围可用性规则达到了相同的效果。
var a = Singleton.getInstance('foo'); var b = new a.constructor('bar');
最清晰的答案应该是Addy Osmani的《学习JavaScript设计模式》一书中的答案。
var mySingleton = (function () {
// Instance stores a reference to the Singleton
var instance;
function init() {
// Singleton
// Private methods and variables
function privateMethod(){
console.log( "I am private" );
}
var privateVariable = "Im also private";
var privateRandomNumber = Math.random();
return {
// Public methods and variables
publicMethod: function () {
console.log( "The public can see me!" );
},
publicProperty: "I am also public",
getRandomNumber: function() {
return privateRandomNumber;
}
};
};
return {
// Get the Singleton instance if one exists
// or create one if it doesn't
getInstance: function () {
if ( !instance ) {
instance = init();
}
return instance;
}
};
})();
我相信这是最简单/最简洁,最直观的方式,尽管它需要ES7:
export default class Singleton { static instance; constructor(){ if(instance){ return instance; } this.state = "duke"; this.instance = this; } }
源代码来自:adam-bien.com
new Singleton()
这怎么了
function Klass() {
var instance = this;
Klass = function () { return instance; }
}
Test = Klass; t1 = new Test(); t2 = new Test();
-没有机会重命名类或选择其他名称空间。
这是解释javascript中单例模式的简单示例。
var Singleton=(function(){
var instance;
var init=function(){
return {
display:function(){
alert("This is a Singleton patern demo");
}
};
};
return {
getInstance:function(){
if(!instance){
alert("Singleton check");
instance=init();
}
return instance;
}
};
})();
// In this call first display alert("Singleton check")
// and then alert("This is a Singleton patern demo");
// It means one object is created
var inst=Singleton.getInstance();
inst.display();
// In this call only display alert("This is a Singleton patern demo")
// it means second time new object is not created,
// it uses the already created object
var inst1=Singleton.getInstance();
inst1.display();
我需要几个单身人士:
所以这就是我想出的:
createSingleton ('a', 'add', [1, 2]);
console.log(a);
function createSingleton (name, construct, args) {
window[name] = {};
window[construct].apply(window[name], args);
window[construct] = null;
}
function add (a, b) {
this.a = a;
this.b = b;
this.sum = a + b;
}
args必须是Array才能起作用,因此,如果您有空变量,只需传入[]
我在函数中使用了窗口对象,但是我可以传入一个参数来创建自己的范围
名称和构造参数只是window []可以工作的字符串,但是通过一些简单的类型检查,window.name和window.construct也可以。
这样,只是确保该类不再是新的。
这样,您可以使用instanceof
op,也可以使用原型链继承该类,它是一个常规类,但是不能新建它,如果yuu要获取实例仅使用getInstance
function CA()
{
if(CA.instance)
{
throw new Error('can not new this class');
}else{
CA.instance = this;
}
}
/**
* @protected
* @static
* @type {CA}
*/
CA.instance = null;
/** @static */
CA.getInstance = function()
{
return CA.instance;
}
CA.prototype =
/** @lends CA#*/
{
func: function(){console.log('the func');}
}
// initilize the instance
new CA();
// test here
var c = CA.getInstance()
c.func();
console.assert(c instanceof CA)
// this will failed
var b = new CA();
如果您不想公开该instance
成员,只需将其放入闭包即可。
以下是我实现单例模式的摘录。这是在面试过程中发生的,我觉得我应该把它捕捉到某个地方。
/*************************************************
* SINGLETON PATTERN IMPLEMENTATION *
*************************************************/
//since there are no classes in javascript, every object is technically a singleton
//if you don't inherit from it or copy from it.
var single = {};
//Singleton Implementations
//Declaring as a Global Object...you are being judged!
var Logger = function() {
//global_log is/will be defined in GLOBAL scope here
if(typeof global_log === 'undefined'){
global_log = this;
}
return global_log;
};
//the below 'fix' solves the GLOABL variable problem but
//the log_instance is publicly available and thus can be
//tampered with.
function Logger() {
if(typeof Logger.log_instance === 'undefined'){
Logger.log_instance = this;
}
return Logger.log_instance;
};
//the correct way to do it to give it a closure!
function logFactory() {
var log_instance; //private instance
var _initLog = function() { //private init method
log_instance = 'initialized';
console.log("logger initialized!")
}
return {
getLog : function(){ //the 'privileged' method
if(typeof log_instance === 'undefined'){
_initLog();
}
return log_instance;
}
};
}
/***** TEST CODE ************************************************
//using the Logger singleton
var logger = logFactory();//did i just gave LogFactory a closure?
//create an instance of the logger
var a = logger.getLog();
//do some work
//get another instance of the logger
var b = logger.getLog();
//check if the two logger instances are same?
console.log(a === b); //true
*******************************************************************/
在我的要点页面上可以找到相同的内容
function Unicode()
{
var i = 0, unicode = {}, zero_padding = "0000", max = 9999;
//Loop through code points
while (i < max) {
//Convert decimal to hex value, find the character, then pad zeroes to the codepoint
unicode[String.fromCharCode(parseInt(i, 16))] = ("u" + zero_padding + i).substr(-4);
i = i + 1;
}
//Replace this function with the resulting lookup table
Unicode = unicode;
}
//Usage
Unicode();
//Lookup
Unicode["%"]; //returns 0025
您可以像下面的示例中那样使用装饰器来完成TypeScript:
class YourClass {
@Singleton static singleton() {}
}
function Singleton(target, name, descriptor) {
var instance;
descriptor.value = () => {
if(!instance) instance = new target;
return instance;
};
}
然后像这样使用单身人士:
var myInstance = YourClass.singleton();
在撰写本文时,JavaScript引擎中不容易使用装饰器。您需要确保JavaScript运行时实际上启用了装饰器,或者使用Babel和TypeScript等编译器。
还要注意,单例实例是“惰性”创建的,即仅在您首次使用它时才创建。
模块模式:“更具可读性”。您可以轻松查看哪些方法是公共方法,哪些是私有方法
var module = (function(_name){
/*Local Methods & Values*/
var _local = {
name : _name,
flags : {
init : false
}
}
function init(){
_local.flags.init = true;
}
function imaprivatemethod(){
alert("hi im a private method");
}
/*Public Methods & variables*/
var $r = {}; //this object will hold all public methods.
$r.methdo1 = function(){
console.log("method1 call it");
}
$r.method2 = function(){
imaprivatemethod(); //calling private method
}
$r.init = function(){
inti(); //making init public in case you want to init manually and not automatically
}
init(); //automatically calling init method
return $r; //returning all publics methods
})("module");
现在您可以使用诸如
module.method2(); //->我正在通过公共方法alert(“ hi im a private method”)调用私有方法
单身人士:
确保一个类只有一个实例,并提供对其的全局访问点。
单例模式将特定对象的实例数限制为一个。该单个实例称为单例。
Singleton对象被实现为直接匿名函数。该函数立即执行,方法是将其包装在方括号中,然后再加上两个括号。之所以称为匿名,是因为它没有名称。
示例程序
var Singleton = (function () {
var instance;
function createInstance() {
var object = new Object("I am the instance");
return object;
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
function run() {
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
alert("Same instance? " + (instance1 === instance2));
}
run()
对我来说,最简单/最干净的意思也就是简单地理解并且没有任何麻烦,就像在Java版本的讨论中所讨论的那样:
从我的角度来看,最简单/最干净的答案是:
https://stackoverflow.com/a/70824/1497139
而且它只能部分翻译成JavaScript。Javascript中的一些区别是:
但是,鉴于最新的ECMA语法,可能更接近:
Singleton模式作为JavaScript类示例
class Singleton {
constructor(field1,field2) {
this.field1=field1;
this.field2=field2;
Singleton.instance=this;
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance=new Singleton('DefaultField1','DefaultField2');
}
return Singleton.instance;
}
}
用法示例
console.log(Singleton.getInstance().field1);
console.log(Singleton.getInstance().field2);
结果示例
DefaultField1
DefaultField2
function Once() {
return this.constructor.instance || (this.constructor.instance = this);
}
function Application(name) {
let app = Once.call(this);
app.name = name;
return app;
}
如果您要上课:
class Once {
constructor() {
return this.constructor.instance || (this.constructor.instance = this);
}
}
class Application extends Once {
constructor(name) {
super();
this.name = name;
}
}
测试:
console.log(new Once() === new Once());
let app1 = new Application('Foobar');
let app2 = new Application('Barfoo');
console.log(app1 === app2);
console.log(app1.name); // Barfoo
如果要使用类:
class Singleton {
constructor(name, age) {
this.name = name;
this.age = age;
if(this.constructor.instance)
return this.constructor.instance;
this.constructor.instance = this;
}
}
let x = new Singleton('s',1);
let y = new Singleton('k',2);
上面的输出将是:
console.log(x.name,x.age,y.name,y.age) // s 1 s 1
使用函数编写Singleton的另一种方式
function AnotherSingleton (name,age) {
this.name = name;
this.age = age;
if(this.constructor.instance)
return this.constructor.instance;
this.constructor.instance = this;
}
let a = new AnotherSingleton('s',1);
let b = new AnotherSingleton('k',2);
上面的输出将是:
console.log(a.name,a.age,b.name,b.age)// s 1 s 1