JavaScript设计模式-单例模式

221 阅读3分钟

单例模式

单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点

1、实现单例模式

实现单例模式并不难,无非就是使用一个变量来标志当前是否已经为某个类创建过实例,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。代码如下所示:

var Singleton = function (name) {
  this.name = name
}

Singleton.prototype.getName = function () {
  console.log(this.name)
}

Singleton.getInstance = (function () {
  let instance = null
  return function (name) {
    if (!instance) {
      instance = new Singleton(name)
    }
    return instance
  }
})()

可以通过下面的测试代码测试是否只有一个实例:

var a = Singleton.getInstance('tom')
var b = Singleton.getInstance('jack')

console.log(a === b) // true
  • 虽然上面通过 Singleton.getInstance 来获取 Singleton 类的唯一对象,实现了单例模式的编写,但这段单例模式代码的意义不大。
  • 增加了这个类的“不透明性”,Singleton 使用者必须知道这是一个单例类,跟以往通过 new XXX 的方式获取对象不同,偏要使用 Singleton.getInstance 来获取对象。
  • 还是能通过new XXX方式来获取对象。

2、透明的单例模式

现在我们的目标是实现一个“透明”的单例类,用户从这个类创建对象时,可以像使用其他任何普通类一样 new XXX。代码如下:

var Singleton = (function () {
  var instance = null
  var Singleton = function (name) {
    if (instance) {
      return instance
    }
    this.name = name
    return (instance = this)
  }
  return Singleton
})()

var a = new Singleton('hh')
var b = new Singleton('kk')

console.log(a === b) // true

这段代码中,Singleton 的构造函数实际上负责了两件事情:

  • 创建对象
  • 保证只有一个对象

现在有个问题,某天需要这个类创建多个实例,那么不得不把控制创建唯一对象的那一段去掉,这种修改会很麻烦,下面使用代理的方式来解决这个问题

3、用代理实现单例模式

先看代码:

var Singleton = function (name) {
  this.name = name
}

Singleton.prototype.getName = function () {
  console.log(this.name)
}

var proxySingleton = (function () {
  var instance = null
  return function (name) {
    if (!instance) {
      instance = new Singleton(name)
    }
    return instance
  }
})()

var a = new proxySingleton('hh')
var b = new proxySingleton('kk')

console.log(a === b)

上面引入了代理类来实现单例模式,与之前不同的是,把负责管理单例的逻辑移到了代理类 ProxySingleton 中,Singleton 就变成了一个普通类

这个也是缓存代理的应用之一

4、惰性单例

惰性单例指的是在需要的时候才创建对象实例。惰性单例是单例模式的重点,这种技术非常有用,开头的例子就使用这种技术,instance 实例对象总是在调用 Singleton.getInstance 的时候才被创建

5、面试题目

5.1、实现一个 Storage

  • 描述:

实现 Storage,使得该对象为单例,基于 localStorage 进行封装。实现方法 setItem(key,value)getItem(key)

  • 思路:

碰到单例模式的题目,首先要知道 getInstanceinstance 是干嘛用的,然后实现上,把判断逻辑写入静态方法或者构造函数里都没关系

  • 实现:
class Storage {
  static getInstance() {
    if (!Storage.instance) {
      Storage.instance = new Storage()
    }
    return Storage.instance
  }

  getItem(key) {
    return localStorage.getItem(key)
  }
  setItem(key, value) {
    return localStorage.setItem(key, value)
  }
}

const storage1 = Storage.getInstance()
const storage2 = Storage.getInstance()

storage1.setItem('name', '李雷')
// 李雷
storage1.getItem('name')
// 也是李雷
storage2.getItem('name')

// 返回true
storage1 === storage2