在 JavaScript 中实现单例的最简单/最干净的方法

IT技术 javascript function design-patterns singleton
2021-02-07 20:57:20

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

6个回答

我认为最简单的方法是声明一个简单的对象字面量:

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

这就是所谓module模式,它基本上可以让你来封装对象私有成员,通过采取利用的优势关闭

如果要防止修改单例对象,可以使用 ES5方法冻结它Object.freeze

这将使对象不可变,防止对其结构和值进行任何修改。

如果你使用的是 ES6,你可以很容易地使用ES module来表示单例,你甚至可以通过在module范围内声明变量来保持私有状态

// my-singleton.js
const somePrivateState = []

function privateFn () {
  // ...
}

export default {
  method1() {
    // ...
  },
  method2() {
    // ...
  }
}

然后你可以简单地导入单例对象来使用它:

import myInstance from './my-singleton.js'
// ...
@Tom,是的,该模式诞生于基于类的 OOP 语言 - 我记得很多涉及静态getInstance方法和私有构造函数的实现 - 但 IMO,这是构建单例对象的最“简单”的方法在 Javascript 中,最后它达到了相同的目的 - 单个对象,您不能再次初始化(没有构造函数,它只是一个对象)-。关于你链接的代码,它有一些问题,交换ab变量声明和测试a === window干杯。
2021-03-10 20:57:20
这不应该是公认的答案。这根本就不是单身!这只是一个全局变量。两者之间有天壤之别。
2021-03-24 20:57:20
+1 在具有全局变量的语言中寻找“单例模式”是不是有点奇怪???
2021-03-24 20:57:20
使用module模式,公共成员如何访问另一个公共成员?即,如何publicMethod1调用publicMethod2
2021-03-25 20:57:20
@Victor – 在这种语言中寻找“单例模式”并不奇怪。许多面向对象的语言使用全局变量,并且仍然使用单例。单例不仅保证给定的类只有一个对象。Singleton 还有几个特点:1)它应该在第一次使用时初始化(这不仅意味着延迟初始化,而且保证对象真的准备好使用)2)它应该是线程安全的。module模式可以替代单例模式,但只能在浏览器中(并不总是)。
2021-04-08 20:57:20

我认为最干净的方法是:

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();
整个“getInstance”部分对我来说更像是工厂。
2021-03-12 20:57:20
小提琴坏了
2021-03-14 20:57:20
这不是单例,因为您可以使用 Object.create 创建它的多个实例。
2021-03-16 20:57:20
备注:可以使用delete instance.constructor以下方法再次读取原始构造函数x = SingletonClass.getInstance();delete x.constructor;new x.constructor;
2021-04-01 20:57:20
var test = SingletonClass.getInstance() - 看起来不像 JS 那样干净。其他以 a = new Foo(); 结尾的灵魂 b = 新 Foo(); a === b //真
2021-04-08 20:57:20

我不确定我是否同意将module模式用作单例模式的替代品。我经常看到在完全不需要单例的地方使用和滥用单例,而且我确信module模式填补了程序员否则会使用单例的许多空白。然而,module模式不是单例。

module模式:

var foo = (function () {
    "use strict";
    function aPrivateFunction() {}
    return { aPublicFunction: function () {...}, ... };
}());

module模式中初始化的所有内容都在Foo声明时发生此外,module模式可用于初始化构造函数,然后可以多次实例化该构造函数。虽然module模式是许多工作的正确工具,但它并不等同于单例。

单例模式:

简写
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();
}
长格式,使用module模式
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
我从来没有说过单身人士是一个好主意还是坏主意。我说你的单例实现比它需要的复杂得多,因为你混淆了 Java 的限制和模式,好像你根本不理解它。当您可以在 Javascript 中简单地使用匿名函数时,这就像通过创建构造函数和方法来实现策略模式。
2021-03-10 20:57:20
由于其局限性,这仅适用于 Java 等语言。var singleton = {}是你如何在 Javascript 中实现单例
2021-03-12 20:57:20
@Esalija,“在基于原型的编程语言中,使用对象而不是类......”JavaScript 具有类的概念,因此不适用。
2021-04-04 20:57:20
@Esailija,在我看来,您似乎不了解单例模式。单例模式是一种设计模式,它将类的实例化限制为一个对象。var singleton = {}不符合这个定义。
2021-04-06 20:57:20

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"

你缺static getInstance()方法吗?或者你如何使用这个实现?
2021-03-17 20:57:20
@akinuri 我知道私有类字段还没有实现。此解决方案特定于 ES6。developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-03-27 20:57:20
嗨,你能帮我了解 _instance 和 instance 之间的区别吗,因为我正在使用实例并且代码不起作用
2021-03-31 20:57:20
instance没有技术差异_instance这只是编程语言中的命名约定,我们将私有变量命名为带有下划线的前缀。我怀疑您的代码不起作用的原因是您正在使用this.instance而不是MyClass.instance
2021-04-01 20:57:20
这可以通过使用将保存实例的静态私有变量来改进,例如static #instance = null;. 否则,您可以修改该_instance属性(将其设置为null)以创建第二个实例。
2021-04-06 20:57:20

ECMAScript 2015 (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
如果 Singleton 是某个其他类的包装器并且只有该instance字段,则冻结是有意义的目前(instance设置为this)这个类也可能有其他字段,冻结没有意义imo。
2021-04-06 20:57:20