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


当前回答

我需要几个单人间:

延迟初始化 初始参数

这就是我想到的:

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对象,但我本可以传入一个参数来创建自己的作用域 name和构造参数只是字符串,以便window[]工作,但有一些简单的类型检查,window.name和window.name。构造也是可能的。

其他回答

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的函数作用域可用性规则,我们也达到了同样的效果。

2015年ECMAScript (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

如果你想使用类:

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

剥猫皮的方法不止一种:)根据你的口味或特定需求,你可以应用任何一种建议的解决方案。我个人倾向于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
}

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

模块模式:

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