单例模式 (Singleton Pattern)又称为单体模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。也就是说,每次使用同一个类创建的新对象都是完全相同的。

单例模式是设计模式中相对较为容易理解、容易上手的一种模式,同时因为其具有广泛的应用场景,也是面试题里的常客

什么场景需要单例模式呢?

  • 一些游戏,如:植物大战僵尸,我们肯定希望每次玩的时候都是从之前的存档继续,假如,第二天来玩是从第一关卡开始,那我直接把电脑扔到了窗外。而有了存档,那每次玩都在相同的存档处继续,这就用到了单例。
  • 网站的登录框、购物车等
  • redux、vuex等状态管理

单例模式的实现

我们暂且不讨论如何实现单例模式,先思考下如何保证一个类仅有一个实例呢?
一般情况下,我们创建了一个类(本质是构造函数)后,会通过new关键字调用构造函数从而生成实例对象。就像这样:

class Man {
  callWife() {
    console.log('已经给您的老婆小丽打电话');
  }
}

const man1 = new Man();
const man2 = new Man();

console.log(man1 === man2); // false

上面我们先new了一个man1,又new了一个man2,很明显man1和man2是相互独立的对象,各占一块内存空间。而单例模式的效果是:不管我们创建多少次对象,只返回给我们第一次创建的那个唯一实例

要实现这一点,就需要构造函数具备判断自己是否已经创建过一个实例的能力。我们把这段逻辑写成一个静态方法:

class Man {
  callWife() {
    console.log('已经给您的老婆小丽打电话');
  }
  static getInstance() {
    if(!Man.instance) {
      Man.instance = new Man();
    }
    return Man.instance;
  }
}

const man1 = Man.getInstance();
const man2 = Man.getInstance();

console.log(man1 === man2); // true

除了这种方式之外,getInstance方法的逻辑还可以用闭包来实现:

Man.getInstance = (function() {
  let instance = null;
  return function() {
    if(!instance) {
      instance = new Man();
    }
    return instance;
  }
})();

现在,在getInstance方法的判断下,不管调用多少次,Man都只会返回给我们同一个实例,man1和man2现在都指向这个唯一的实例。

Vuex中的单例模式

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

在Vue中,组件间通信最常用的是props(仅限于父子组件通信),复杂一点的(比如兄弟组件间通信)可以通过父组件来间接搞定。

但当组件非常多、关系非常复杂时,上面提到的通信方式就变的难以维护。这时最好的做法是将共享的数据抽出来、放在全局,供组件们按照一定的的规则去存取数据,保证状态以一种可预测的方式发生变化。于是便有了 Vuex,这个用来存放共享数据的唯一数据源,就是 Store。关于 Vuex 的细节,大家可以参考Vuex的官方文档

Vuex如何确保Store的唯一性

先来看下如何在项目中引入Vuex:

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

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

通过调用Vue.use()方法,安装 Vuex 插件。Vuex 插件是一个对象,它在内部实现了一个 install 方法,这个方法会在插件安装时被调用,从而把 Store 注入到Vue实例里去。

在install方法里,实现的逻辑和getInstance方法很相似:

let Vue // Vue的作用和上面的instance一样
...

export function install (_Vue) {
  // 判断传入的Vue实例对象是否已经被install过(是否有了唯一的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的钩子函数里
  applyMixin(Vue)
}

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

Scroll Up