最近趁着公司项目不是很急,抽空看了会JavaScript设计模式相关的资料。主要学习了其中的单例模式,下面就根据学习,分享我所理解的单例模式是咋样的。

什么是单例模式

我所理解的单例模式就是通过构造函数生成一个唯一实例,但是这种说法不太严谨。更官方一点的解释就是:保证一个类仅有唯一实例,并且能够提供一个全局访问点就叫单例模式

一般定义一个类之后,可以通过new调用构造函数生成多个实例对象,比如:


class Person{
    static msg(){
        console.log('hello world')
    }
}

const p1 = new Person()
const p2 = new Person()

p1 === p2 //false

以上定义了一个Person对象,然后通过new分别生成了p1和p2两个实例对象。很显然p1和p2不相等,这是因为p1和p2指向的是两块不同的存储地址。那么单例模式就是无论怎么创建实例,永远返回第一次创建的那个唯一实例。
那么要实现这个方法,就得先判断构造函数是否生成过一个实例,如果生成了实例就返回这个实例,否则创建一个实例。那么一开始思路就是:


class SingleCase(){
    static getInstance() {
        // 判断是否已经new过1个实例
        if (!SingleCase.instance) {
            // 若这个唯一的实例不存在,那么先创建它
            SingleCase.instance = new SingleDog()
        }
        // 如果这个唯一的实例已经存在,则直接返回
        return SingleCase.instance
    }
}

const p1 = new SingleCase()
const p2 = new SingleCase()
p1 === p2 // true

除此之外还有一个写法,getInstance的逻辑用闭包来实现:


class SingleCase {

  static getInstance = (function () {
    // 定义自由变量instance,模拟私有变量
    let instance = null
    return function () {
      // 判断自由变量是否为null
      if (!instance) {
        // 如果为null则new出唯一实例
        instance = new SingleCase()
      }
      return instance
    }
  })()
}

const p1 = new SingleCase.getInstance()
const p2 = new SingleCase.getInstance()
p1 === p2

另一种实现方法

上面两种方法都是借助函数方法来实现的,我在想有没有一种可能是通过变量来实现?顺着这个想法,尝试着实现下:

class S{
  constructor(){
    if(!S.instance){
      S.instance = new S()
    }else{
      return S.instance
    }
  }
}
const p = new S()
// result : Maximum call stack size exceeded

执行之后发现结果并没有到达预期效果,这是因为第一次new的时候判断当前是否有instance变量,发现没有instance则进入赋值操作。这一步很关键的一点是赋值前先执行了new操作,则有又执行了一遍constructor里面的内容。从而导致无限循环,最终栈溢出。
如何解决这个问题,核心思路就是要在new的时候第一次判断下,具体实现如下:

class S {
  constructor() {
    if (S.status) {
      S.status = false
    } else {
      if (!S.instance) {
        S.status = true
        return S.instance = new S()
      } else {
        return S.instance
      }
    }
  }
}
const p1 = new S()
const p1 = new S()

p1 === p2  //true

new S()第一次的时候按顺序执行,判断是否有status,发现没有就进else里面,通过else里面判断是否已经new过实例,发现没有就给一个状态标记。然后再生成一个实例,程序再次走进constructor,发现状态为true。则什么也不做,此时实例已经生成。然后返回instance属性接受这个实例。此时第一次new 操作已经完成。
第二次再new的时候,判断status为false,然后再判断S.instance是否存在,此时已经存在,则最终返回此实例。

单例模式的应用

dialog弹窗是非常适合单例模式的应用,因为弹窗只有一个,可以通过传参生成不同的弹窗实例。刚好最近公司有个项目用到了,就分享一下。

创建弹窗实例//

/**
  @param {String} title 弹窗标题
  @param {String} content 弹框内容
  @param {String} cancelTex 取消文字
  @param {Function} cancel 取消执行函数
  @param {Function} confirm 确认执行函数
*/

let DialogModal = (function () {
    var modal = null, mask = null;
    return function ({ title, content, cancelTex, cancel, confirm }) {
        if (!modal) {
            modal = document.createElement('div')
            mask = document.createElement('div')
            mask.className = 'mask';
            mask.setAttribute('id', 'Mask')
            modal.setAttribute('class', 'dialog')
        }
        modal.innerHTML = `
                            <p class="modal-title">
                            ${title}
                            </p>
                            <div class="modal-content">
                            ${content}
                            </div>
                            <div class="modal-footer">
                            <p>${cancelTex}</p>
                            <p>去看看</p>
                            </div>
        `
        modal.style.display = 'block'
        mask.style.display = 'block'
        modal.querySelector('#cancel').onclick = function () {
            modal.style.display = 'none'
            mask.style.display = 'none'
            cancel()
        };
        modal.querySelector('#confirm').onclick = function () {
            modal.style.display = 'none'
            mask.style.display = 'none'
            confirm()
        };
        document.body.appendChild(mask)
        document.body.appendChild(modal)
        // return modal
    }
})()

// Example

new Modal({
    title: '温馨提示',
    content: '确定不去看看优惠券么?',
    cancelTex: '坚决放弃',
      // 取消按钮事件
    cancel: function () {
    },
      //  确认按钮事件
    confirm: function () {
    }
})