JavaScript设计模式与开发实践-设计模式(单例模式)

686 阅读4分钟

设计模式:单例模式.png

单例模式介绍

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

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等。
在JavaScript开发中,单例模式的用途非常广泛。
试想一下,当我们点击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,
无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
实现单例模式原理

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

不透明的单例
        // 静态方法的方式
        function singleton(name) {
            this.name = name
            this.instance = null
        }
        singleton.prototype.getName = function () {
            console.log(this.name)
        }
        singleton.getInstance = function(name) {
            if (!this.instance) {
                this.instance = new singleton(name)
            }
            return this.instance
        }

        let a = singleton.getInstance('sven1')
        let b = singleton.getInstance('sven2')

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

或者

        // 静态方法 + 闭包
        let 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
            }
        })()

        let a = singleton.getInstance('sven1')
        let b = singleton.getInstance('sven2')

        console.log(a === b)
       缺点:
       1.不够"透明",无法使用new来进行类的实例化。
       2.我们通过Singleton.getInstance来获取Singleton类的唯一对象,这种方式相对简单,
       但是有一个问题,就是增加了这个类的"不透明性",Singleton类的使用者必须知道这是一个单例类,
       跟以往不通过new的方式来获取对象不同,这里偏要使用Singleton.getInstance来获取对象。
透明的单例模式
      let CreateSington = (function () {
        let instance;
        return function (name) {
          if (instance) {
            return instance;
          }
          this.name = name;
          return (instance = this);
        };
      })();

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

      let Winner = new CreateSington('Winner')
      let Looser = new CreateSington('Loower')

      console.log(Winner === Looser) // true
      console.log(Winner.getName()) // Winner
      console.log(Looser.getName()) // Winner

透明的单例模式,意图统一使用 new 操作符来获取单例对象,而不是 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 a = new CreateDiv('sven1')
        var b = new CreateDiv('sven2')

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

缺点:为了把instance封装起来,我们使用了自执行的匿名函数和闭包,让这个匿名函数返回真正的 Singleton 构造方法,这增加了一些程序的复杂程度,阅读起来也不是很舒服。

            var CreateDiv = function (html) {
                if (instance) {
                    return instance
                }
                this.html = html
                this.init()

                return instance = this
            }
            // CreateDiv 构造函数实际上负责了两件事情。
            // 1. 创建对象和执行初始化init方法,
            // 2. 保证只有一个对象。虽然我们还没接触过 "单一职责原则" 的概念,但是可以明确的是,这是一种不好的做法。至少这个构造函数看起来很奇怪。
            // PS: 假如某天我们需要用这个类,在页面中创建千千万万个的div,
            // 既要让这个类从单例类变成一个普通的可产生多个实例的类。那么我们必须改下CreateDiv函数。
            // 把控制创建唯一对象的那一段去掉。这种修改会给我们带来不必要的烦恼。
代理版的单例模式

通过 "代理"的形式:将 管理类操作 和 对象创建操作进行拆分,实现更小的力度划分,符合 "单一职责原理"。

        // 将管理单例操作,与对象创建操作进行拆分,实现更小粒度划分,符合 "单一职责原则"
        let ProxyCreateSingleton = (function () {
            let instance
            return function (name) {
                // 代理函数仅管控单例
                if (instance) {
                    return instance
                }
                return instance = new Singleton(name)
            }
        })()
        // 独立的Singleton类,处理对象实例。
        let Singleton = function (name) {
            this.name = name
        }
        Singleton.prototype.getName = function () {
            console.log(this.name)
        }

        let Winner = new ProxyCreateSingleton("Winner")
        let Looser = new ProxyCreateSingleton('Looser')

        console.log(Winner === Looser) // true
        console.log(Winner.getName()) // Winner
        console.log(Looser.getName()) // Winner
惰性单例模式

惰性单例指的是 在需要的时候才会创建对象。惰性单例是单例模式的重点,这种技术在实际开发中非常有用。

        // 惰性单例:在需要的时候才创建对象实例:
        function Singleton(fn) {
            let result
            return function () {
                return result || (result = fn.apply(this, arguments))
            }
        }