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


当前回答

关键是要理解闭包背后的重要性。因此,在闭包的帮助下,即使在内部函数内部的属性也将是私有的。

var Singleton = function () {
    var instance;

    function init() {

       function privateMethod() {
           console.log("private via closure");
       }

       var privateVariable = "Private Property";

       var privateRandomNumber = Math.random(); // This is also private

       return {
           getRandomNumber: function () {  // Access via getter in init call
               return privateRandomNumber;
           }
       };
    };

    return {
        getInstance: function () {

            if (!instance) {
                instance = init();
            }
            return instance;
        }
    };
};

其他回答

我认为我已经找到了用JavaScript编程的最简洁的方法,但您需要一些想象力。我是从《JavaScript: the Good Parts》一书中的一个工作技巧中得到这个想法的。

不用new关键字,你可以这样创建一个类:

function Class()
{
    var obj = {}; // Could also be used for inheritance 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();

我认为这是最简洁的方法,因为完整的“类”甚至无法访问。

我不确定我是否同意用模块模式来代替单例模式。我经常看到单例对象在完全没有必要的地方被使用和滥用,我确信模块模式填补了程序员使用单例对象的许多空白。然而,模块模式不是单例的。

模块模式:

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;
}());

在我提供的两个版本的单例模式中,构造函数本身都可以用作访问器:

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
class Singelton {
    static #instance;

    #testValue;

    constructor() {
        if (Singelton.#instance instanceof Singelton) {
            return Singelton.#instance;
        }

        Singelton.#instance = this;
        return Singelton.#instance;
    }

    get testValue() {
        return this.#testValue;
    }

    set testValue(value) {
        this.#testValue = value;
    }
}

测试:

let x = new Singelton();
x.testValue = 123;

let y = new Singelton();

console.log({isSingelton: x === y, testValueFromY: y.testValue});

你可以用下面这个例子中的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这样的编译器。

还要注意的是,单例实例是“惰性”创建的,也就是说,只有当你第一次使用它时才会创建它。

使用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