用JavaScript实现单例模式的最简单/最干净的方法是什么?


当前回答

使用ES6类和私有静态字段。调用Singleton类的新实例将返回相同的实例。实例变量也是私有的,不能在类外部访问。

class Singleton {
  // # is a new Javascript feature that denotes private
  static #instance;

  constructor() {
    if (!Singleton.#instance) {
      Singleton.#instance = this
    } 
    return Singleton.#instance
  }

  get() {
    return Singleton.#instance;
  }
}

const a = new Singleton();
const b = new Singleton();
console.log(a.get() === b.get()) // true
console.log(Singleton.instance === undefined) // true

其他回答

Christian C. Salvadó和zzzzBov的回答都给出了精彩的答案,但只是添加我自己的解释,基于我已经从PHP/Zend框架转向了沉重的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 available 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'

请注意,从技术上讲,自执行匿名函数本身就是一个单例函数,Christian C. Salvadó提供的代码很好地演示了这一点。这里唯一的问题是,当构造函数本身是匿名的时,不可能修改构造函数的原型链。

请记住,在JavaScript中,“公共”和“私有”的概念并不像在PHP或Java中那样适用。但是,通过利用JavaScript的函数作用域可用性规则,我们也达到了同样的效果。

剥猫皮的方法不止一种:)根据你的口味或特定需求,你可以应用任何一种建议的解决方案。我个人倾向于Christian C. Salvadó的第一个解决方案(当你不需要隐私的时候)。

因为这个问题是关于最简单和最干净的,所以这个问题是赢家。甚至:

var myInstance = {}; // Done!

这(引用自我的博客)……

var SingletonClass = new function() {
    this.myFunction() {
        // Do stuff
    }
    this.instance = 1;
}

没有太大的意义(我的博客例子也没有),因为它不需要任何私有变量,所以它几乎与:

var SingletonClass = {
    myFunction: function () {
        // Do stuff
    },
    instance: 1
}

我喜欢将单例模式和模块模式结合起来使用,并将初始化时分支与Global NS检查结合起来使用,并将它们包装在一个闭包中。

在初始化单例后环境不会改变的情况下,使用立即调用的object-literal来返回一个充满实用程序的模块,该模块将持续一段时间。

我没有传递任何依赖项,只是在它们自己的小世界中调用单例对象——唯一的目标是:为事件绑定/解绑定创建一个实用程序模块(设备方向/方向变化也可以在这种情况下工作)。

window.onload = ( function( _w ) {
    console.log.apply( console, ['it', 'is', 'on'] );
    ( {
        globalNS : function() {
            var nameSpaces = ["utils", "eventUtils"],
                nsLength = nameSpaces.length,
                possibleNS = null;

            outerLoop:
            for ( var i = 0; i < nsLength; i++ ) {
                if ( !window[nameSpaces[i]] ) {
                    window[nameSpaces[i]] = this.utils;
                    break outerLoop;
                };
            };
        },
        utils : {
            addListener : null,
            removeListener : null
        },
        listenerTypes : {
            addEvent : function( el, type, fn ) {
                el.addEventListener( type, fn, false );
            },
            removeEvent : function( el, type, fn ) {
                el.removeEventListener( type, fn, false );
            },
            attachEvent : function( el, type, fn ) {
                el.attachEvent( 'on'+type, fn );
            },
            detatchEvent : function( el, type, fn ) {
                el.detachEvent( 'on'+type, fn );
            }
        },
        buildUtils : function() {
            if ( typeof window.addEventListener === 'function' ) {
                this.utils.addListener = this.listenerTypes.addEvent;
                this.utils.removeListener = this.listenerTypes.removeEvent;
            } else {
                this.utils.attachEvent = this.listenerTypes.attachEvent;
                this.utils.removeListener = this.listenerTypes.detatchEvent;
            };
            this.globalNS();
        },
        init : function() {
            this.buildUtils();
        }
    } ).init();
} ( window ) );

对我来说,最干净的方法是:

const singleton = new class {
    name = "foo"
    constructor() {
        console.log(`Singleton ${this.name} constructed`)
    }
}

使用这种语法,您可以确定您的单例是并且将保持惟一的。您还可以享受类语法的甜蜜,并按预期使用它。

(注意,类字段需要节点v12+或现代浏览器。)

另一种方法-只是确保类不能再新的。

这样,您就可以使用instanceof op。此外,您还可以使用原型链来继承类。这是一个普通的课程,但是你不能去学。如果你想获取实例,只需使用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');}
}

// Initialise the instance
new CA();

// Test here
var c = CA.getInstance()
c.func();
console.assert(c instanceof CA)

// This will fail
var b = new CA();

如果不想公开实例成员,只需将其放入闭包中。