你的浏览器不支持canvas

Enjoy life!

javasript - 设计模式 - 单例模式

Date: Author: JM

本文章采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。

一、什么是单例模式

  • 单例模式(Singleton Pattern):保证一个类仅有一个实例,并提供一个访问它的全局访问点
  • 单例模式的核心:确保只有一个实例,并提供全局访问。

二、实现单例模式

  • 原理:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

2.1 “不透明”的单例模式

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

const a = Singleton.getInstance('a')
const b = Singleton.getInstance('b')
console.log(a === b) // true
  • 正常来说,我们都是new xxx来获取一个对象的,然而,从以上代码我们可以看到,是通过Singleton.getInstance()获取对象的,这明显和我们想象中的不一样。
  • 同时,对于不知情者来说(即:不知道这是一个单例类),都会直接var a = new Singleton('bb)来新建一个对象,然而,这又与实际代码的功效不符。
  • 所以以上代码就称它为“不透明”的单例模式,简单理解:这是一个令人有误会的单例模式。

2.2 “透明”的单例模式

  • “透明”的单例模式:我们直接用new Singleton()来使用Singleton这个类,而不用想那么多无谓的东西。
  • CreateDiv 单例类:负责在页面中创建唯一的 div 节点
let CreateDiv = (function () {
  let instance = null
  const CreateDiv = function (html) {

    // 保证只创建一个div
    if (instance) {
      return instance
    }

    this.html = html
    // 创建div
    this.init()
    instance = this
    return instance
  }

  CreateDiv.prototype.init = function () {
    const div = document.createElement('div')
    div.innerHTML  = this.html
    document.body.appendChild(div)
  }

  return CreateDiv
})()

const div = new CreateDiv('html1')
const div1 = new CreateDiv('html2')

console.log(div === div1)
  • 执行上面代码后,你会发现,只有1个div被创建
  • 以上代码尽管实现了单例模式的“透明性”,却暴露了一些缺点:
    • 假设我们某天需要利用这个类,在页面中创建千千万万的 div,即:让这个单例类变成 一个普通的可产生多个实例的类,那我们必须改写 CreateDiv 构造函数,把控制创建唯一对象 的那一段代码去掉,这就是一个“bug”了,太麻烦了。
    • 违反了“单一职责原则”:
      • CreateDiv 的构造函数做了两件事:第一是创建对象和执行初始化 init 方法,第二是保证只有一个对象。
    • 代码看上去很冗杂,不舒服。

2.3 使用代理模式

  • 解决以上问题可以用代理模式。
    // 创建div的类
    function CreateDiv (html) {
      this.html = html
    }
    CreateDiv.prototype.init = function () {
      const div = document.createElement('div')
      div.innerHTML = this.html
      document.body.appendChild(div)
    }

    // 代理模式:负责管理单例
    const ProxySingletonCreateDiv = (function  () {
        let instance = null

        return function (html) {
          if (!instance) {
            instance = new CreateDiv(html)
            instance.init()
          }

          return instance
        }
    })()

    // 测试代码
    const a = new ProxySingletonCreateDiv('html1')
    const b = new ProxySingletonCreateDiv('html2')
    console.log(a === b)
  • 通过使用代理模式之后,你会发现代码结构清晰,功能对应,一看上去就知道这段代码究竟是用来做什么的。

三、总结

  • 单例模式核心:就是只有一个实例
  • 优点:节约资源,控制入口
  • 缺点:拓展困难(所以可以使用代理模式),不可以职责过重

对于本文内容有问题或建议的小伙伴,欢迎在文章底部留言交流讨论。