# 何为单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式

# 单例模式的实现

问题:如何才能保证一个类仅有一个实例

一般情况下,当我们创建了一个类(本质是构造函数),可以通过 new 关键字调用构造函数进而生成任无数个实例对象。比如下面的类:


class SingleDog {
	
	show(){
		console.log('我是一个单身狗')
	}

}


const s1 = new SingleDog()
const s2 = new SingleDong()

s1 === s2		// false

可以看到 s1 和 s2 是两个实例,类似这种 先 new 了一个 s1,又new 了一个 s2 ,很明显 s1 和 s2 之前除了构造器韩式是同一个,没有其它任何瓜葛,两者是相互独立的对象,各占一款内存空间

显然上面的 做法不是单例模式,而单例模式想要实现的是是:不管尝试去创建多少次,它都只会给你返回第一次所创建的实例(并且也是微医的一个实例)

所有要做到这一点,就需要 构造函数 具备判断自己是否已经创建过一个实例的能力。我们仙子啊吧这段判断逻辑携程一个静态方法(其实也可以直接写入构造函数的函数体里):

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

const s1 =  SingleDog.getInstance()
const s2 =  SingleDog.getInstance()

s1 === s2		// true

除了楼上实现方式外,getInstance 的逻辑还可以用闭包来实现:


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

可以看出,在 getInstance 方法的 判断和拦截下,不管调用多少次,SingleDog 都只会返回一个实例。

# 单例模式的应用

近年来,基于 Flux架构的状态管理工具层出不穷,其中应用最广泛的要输 ReduxVuex。无论是 ReduxVuex,它们都实现了一个全局的 Store 用于存储应用的所有状态,这个Store的实现,正式单例模式的典型应用。这里以 Vuex 为例,研究下 单例模式是怎么发光发热的

# 理解Vuex 中的 store

:::info

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此他便作为一个“唯一数据源(SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接的定位任一特定的状态片段,在调试的过程中也能轻易的取得整个应用状态的快照。

------ Vuex官方文档

:::

在 Vue 中,组件之间是独立的,组件间的通信最常用的办法是通过 props 属性传递(限于父子组件),稍微复杂点的(比如兄弟组件)需要通过简单的事件监听或者 eventBus 来解决。

但当组件非常多,组件关系复杂,且嵌套层次很深的时候,这种原始的通信方式会使我们的逻辑边得复杂难以维护,这时最好的做法就是将共享的数据抽出来、放在全局、供组件门按照一定的规则去存取数据,保证状态以一种可预测的方式发生变化。于是便有了 Vuex,这个用来存放共享数据的唯一数据源就是 Store.

所以 在 Vue 实例中,一个Vue实例只能有一个 Vuex,即 一个Vue实例只能对应一个 Store。

# Vuex 如何确保Store 的唯一性

我们先来看看如何在 项目中引入 Vuex

// 安装vuex 插件
Vue.use(Vuex)

// 将store 注入到Vue 实例中
new Vue({
    el:'#app',
    store
})

通过调用 Vue.use() 方法,安装了Vuex插件,Vuex插件是一个对象,它在内部实现了一个install方法,这个方法会在插件安装的时候被调用,从而把 Store 注入到Vue 实例中去,也就是说 每 install 一次,都会尝试给Vue 实例注入一个 Store

在 install 方法里,有一段逻辑和我们楼上的 getInstance 非常相似的逻辑:

let Vue	// 这个 Vue 的作用和楼上的 instance 作用一样
...

export function install(_Vue){
    // 判断传入的Vue实例对象是否已经被install过 Vuex 插件(是否有了唯一的state)
    if(Vue && _Vue === Vue){
        if(process.env.NODE_ENV !== 'production') {
            console.error('[vuex] already installed.Vue.use(Vuex) should be called only once.')
        }
        return 
    }
    // 若没有,则为这个Vue实例对象 install 一个唯一的 Vuex
    Vue = _Vue
    // 将Vuex 的初始化逻辑写进 Vue 的钩子函数里
    applyMinxin(Vue)
}

楼上便是 Vuex 源码中单例模式的实现办法了,套路可以说和我们的 getInstance 如出一辙。通过这种方式,可以保证一个Vue实例(即一个Vue应用)只会被 install 一次 Vuex 插件,所以每个 Vue 实例只会拥有一个全局的 Store.

单例模式除了在 Vuex 中大展身手,我们在 Redux 、jQuery 等许多优秀的前端库里也都能看到单例模式的身影。

最后更新时间: 2/21/2022, 3:29:09 PM