# 何为单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点,这样的模式就叫做单例模式
# 单例模式的实现
问题:如何才能保证一个类仅有一个实例
一般情况下,当我们创建了一个类(本质是构造函数),可以通过 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
架构的状态管理工具层出不穷,其中应用最广泛的要输 Redux
和 Vuex
。无论是 Redux
和Vuex
,它们都实现了一个全局的 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 等许多优秀的前端库里也都能看到单例模式的身影。