单例模式

146 阅读3分钟

定义

保证一个类仅有一个实例,并提供一个访问它的全局反问点

场景

线程池,全局缓存,浏览器中的window对象等

实现单例模式(简单的例子)

var Singleton = function (name) {
  this.name = name;
};
Singleton.instance = null;
Singleton.prototype.getName = function () {
  console.log(this.name);
};
Singleton.getInstance = function name(name) {
  if (!this.instance) {
    this.instance = new Singleton(name);
  }
  return this.instance;
};

var a = Singleton.getInstance("sven1");
var b = Singleton.getInstance("sven2");
console.log(a === b);

我们通过Singleton.getInstance,来获取Singleton类的唯一对象,这种方式相对简单,但有一个问题,就是增加了这个类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,跟通过new xxx的方式来获取对象不同,这里偏要使用Singleton.getInstance来获取对象

透明的单例模式

实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使用其他任何普通类一样。

使用`CreateDiv单例类,它的作用是负责在页面中创建唯一的div子节点

var CreateDiv = (function () {
  var instance;
  var CreateDiv = function (html) {
    if (instance) {
      return instance;
    }
    this.html = html;
    this.init();
    return (instance = this);
  };
  CreateDiv.prototype.init = function () {
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild("div");
  };
  return CreateDiv;
})();
var div1 = new CreateDiv("div1");
var div2 = new CreateDiv("div2");
console.log(div1 === div2);

虽然完成一个透明的单例模式,但它的缺点也是非常明显,使用了自执行的匿名函数和闭包,并且让这个匿名函数返回真正的CreateDiv的构造方法,这增加了一些程序的复杂度,阅读起来也不是很舒服

把上面列子代码进行解耦

把上面例子的代码进行解耦,让它更加容易阅读,减轻复杂度,首先在CreateDiv构造函数中,把负责管理单例的代码移除出去,使它成为普通类

var CreateDiv = function (html) {
  this.html = html;
  this.init();
};
CreateDiv.prototype.init = function () {
  var div = document.createElement("div");
  div.innerHTML = this.html;
  document.body.appendChild(div);
};

var ProxySingLetonCreateDiv = (function () {
  var instance;
  return function (html) {
    if (!instance) instance = new CreateDiv(html);
    return instance;
  };
})();

JavaScript 中的单例模式

前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从类中创建而来。在以类为中心的语言中,这是很自然的做法。

但JavaScript其实是一门无类语言,也正因为如此,生搬单例模式的概念并无意义。在js中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它创建一个类呢?

单例模式的核心是确保只有一个实例,并提供全局访问

全局变量不是单例模式,但在js开发中,我们经常会把全局变量当成单例模式来使用

列如

  • var a = {}

当用这种方式创建对象a时,对象a确实是独一无二的。如果a变量被声明在全局作用域下,则我们可以在代码中的任意位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就满足单例模式的两个条件

但全局变量存在很多问题,它很容易造成命名空间污染。就像上面的对象a一样,随时可能被别人覆盖。开发中有必要减少全局变量的使用,即时需要,也要把它的污染降到最低。