JavaScript设计模式学习之单例模式

32 阅读2分钟

定义

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

使用场景

单例模式可以用来管理全局状态和共享资源,比如登陆弹窗

单例模式的几种实现方法

单例模式的实现并不困难,从它的定义中我们就可以凭借直觉推断出实现方法,即可以通过一个变量标识来辨别该类是否已经创建。 下面我们通过代码来实现:

let singAlert = function(title, show = false){
    this.title = title
    this.show = show
}

singAlert.instance = null
singAlert.prototype.open = function(){
    this.show = !this.show
}
singAlert.getInstance = function(title){
    if(!this.instance){
        this.instance = new singAlert(title)
    }
    return this.instance
}

let a = singAlert.getInstance('弹窗一')
let b = singAlert.getInstance('弹窗二')

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

通过singAlert.getInstance方法就可以获取singAlert的唯一实例。缺点就在于使用方法上的不透明,该函数的使用者必须清楚的知道需要通过getInstance方法才可以获取实例。除了方法的封装者知道外,其他人很难了解到这一点。

针对这个缺点我们可以通过IIFE来改进,让该函数看起来更加直观,使用起来更方便。

let singAlert = (function(){
    let instance = null
    let div = null
    let singAlert = function(title, show = false){
        if(!instance){
            this.init(title, show)
            instance = this
        }
        return instance
    }
    singAlert.prototype.init = function(title, show){
        this.show = show
        this.title = title
        div = document.createElement('div')
        div.innerHTML = this.title
        div.style.display = this.show ? 'block' : 'none'
        document.body.appendChild(div)
    }
    singAlert.prototype.open = function(){
        this.show = true
    }
    singAlert.prototype.hide = function(){
        this.show = false
    }
    return singAlert
})()

let a = new singAlert('弹窗一')
let b = new singAlert('弹窗二')

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

上述代码虽然完成了单例模式,但是仍然存在一些不足,一个函数内既有创建对象也存在着dom操作,这显然违反了我们的单一职责原则。 现在我们可以将两个逻辑进行拆分抽离:

// 对单例的管理进行封装
let getSingleInstance = function(fn){
    let result
    return function(){
        if(!result){
            result = fn.apply(this, arguments)
        }
        return result
    }
}

//dom操作
let createDiv = function(title, show){
    let div = document.createElement('div')
    div.innerHTML = title
    div.id = 'btn'
    div.style.display = show ? 'block' : 'none'
    document.body.appendChild(div)
    return div
}

// getSingleInstance负责单例管理,createDiv负责dom创建,两者合并就构成了一个单例模式。
// getSingleInstance负责接收一个函数,并对该函数进行单例模式的管理,将其返回值进行返回
let createSingleInstance = getSingleInstance(createDiv)
let bindAlertDiv = createSingleInstance(title='弹窗一', show=true)

//接下来可以对其他的操作方法以及事件进行绑定封装
function open(div){
    div.style.display = 'block'
}
function hide(div){
    div.style.display = 'none'
}
//绑定点击事件
document.getElementById('btn').onclick = function(){
    console.log('click btn...')
    hide(bindAlertDiv)
}

总结

实际上如果你在日常工作中留神的话就会发现,单例模式是我们经常会碰到的一种模式。比如Vuex,一些UI组件中的Message组件等。