介绍

在编写程序时,有些实例对象通常只需要创建一次,在整个程序环境中保持唯一性,而创建这种”唯一”的实例对象,就用到了单例模式。实现标准的单例模式并不难,只要用一个变量来标志当前是否已经为某个类创建过对象,如果是,则下次创建该类的实例时直接把已存在的实例对象返回,否则就创建一个实例对象。

单例逻辑

示例代码:

  1. var Singleton = (function() {
  2. var instance = null;
  3. var Singleton = function(name) {
  4. if (!instance) {
  5. this.name = name;
  6. instance = this;
  7. }
  8. return instance;
  9. }
  10. return Singleton;
  11. })();
  12. var o1 = new Singleton("object 1");
  13. var o2 = new Singleton("object 2");
  14. console.log(o1); // 输出: {name:"object 1"}
  15. console.log(o2); // 输出: {name:"object 1"}
  16. console.log(o1 === o2); // 输出: true

解析:

以上代码生成了一个单例类 Singleton,其内部使用闭包将生成的实例对象保存进一个 instance 变量中,然后在内部定义了一个真正的 Singleton 类,在这个类里面,我们先判断是否实例对象已生成(instance 变量是否为空),如果已生成就将其返回,否则就初始化属性等。

接着,传入不同的参数生成两个看似完全不同的实例对象,但是在控制台输出日志后我们发现,这两个实例对象的内容都是一样的,对这两个实例对象进行完全相等判断,发现返回的是 true!由此证明,我们创建的这个 Singleton 用到了单例模式逻辑。

代理类接入

前面实现的单例模式逻辑代码有不足的地方,单例模式的逻辑和创建对象的逻辑混在一起,这不符合”单一职责原则”,为了实现更完美的单例模式逻辑,我们可以借助代理类。以下是接入代理类后实现的单例逻辑代码:

  1. // 分离出的创建对象的代码
  2. var Singleton = function(name) {
  3. this.name = name;
  4. }
  5. // 单例逻辑代码放在代理类中
  6. var ProxySingleton = (function() {
  7. var instance = null;
  8. return function(name) {
  9. if (!instance) {
  10. instance = new Singleton(name);
  11. }
  12. return instance;
  13. }
  14. })();
  15. var o1 = new ProxySingleton('new object1');
  16. var o2 = new ProxySingleton('new object2');
  17. console.log(o1); // 输出: {name:"object 1"}
  18. console.log(o2); // 输出: {name:"object 1"}
  19. console.log(o1 === o2); // 输出: true

使用代理类的好处是,将负责创建对象的 Singleton 类提取到外面,它成了一个普通的类,而管理单例逻辑的代码则在 ProxySingleton 类中,从而实现了功能上的分离。

JavaScript单例模式实现

前面讲解的无论是单例逻辑代码还是接入代理类的单例模式代码,都是传统面向对象语言中的实现,而 JavaScript 中实现单例不必那么复杂。

首先,我们重温下 JavaScript 的语言特性,主要有以下两点:

  1. 无类;
  2. 基于对象。

正因为 JavaScript 无类,创建一个”对象”就可以替代所谓的”类”,所以,又为什么要生搬”类”的那一套呢?

JavaScript 中的单例模式逻辑,其实应该是这样的:

  1. var singleton = (function() {
  2. var obj = null;
  3. return function(name) {
  4. if (!obj) {
  5. obj = {
  6. name: name
  7. }
  8. }
  9. return obj;
  10. }
  11. })();
  12. var o1 = singleton("new object1");
  13. var o2 = singleton("new object2");
  14. console.log(o1); // 输出: {name:"object 1"}
  15. console.log(o2); // 输出: {name:"object 1"}
  16. console.log(o1 === o2); // 输出: true

以上这段代码依然是不完美的,因为创建对象的逻辑和单例模式的逻辑又混在一起了,改造的方法很简单,请见下面代码:

  1. // 创建对象的逻辑代码
  2. var createObject = function(name) {
  3. return {
  4. name: name
  5. }
  6. }
  7. // 通用的单例模式逻辑代码
  8. var getSingle = function(fn) {
  9. var result;
  10. return function() {
  11. return result || (result = fn.apply(this, arguments));
  12. }
  13. }
  14. // 单例化
  15. var createSingleObject = getSingle(createObject);
  16. var o1 = createSingleObject("new object1");
  17. var o2 = createSingleObject("new object2");
  18. console.log(o1); // 输出: {name:"object 1"}
  19. console.log(o2); // 输出: {name:"object 1"}
  20. console.log(o1 === o2); // 输出: true