单例模式--理解及实际应用

什么叫单例模式

在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

单例模式的实现思路

简单的字面量构造器

 function User(name , age, career) {
      this.name = name
      this.age = age
      this.career = career 
  }
 const lilith1  = new User('lilith', 23 , 'coder') 
 const lilith  = new User('lilith', 23 , 'coder')
 lilith1   ===  lilith  //false

楼上我们先 new 了一个 s1,又 new 了一个 s2,很明显 s1 和 s2 之间没有任何瓜葛,两者是相互独立的对象,各占一块内存空间。而单例模式想要做到的是,不管我们尝试去创建多少次,它都只给你返回第一次所创建的那唯一的一个实例。

要做到这一点,就需要构造函数具备判断自己是否已经创建过一个实例的能力。然后缓存当前实例instance

function User(name , age, career) {
      // 判断是否存在实例
      if (typeof User.instance === 'object') {
          return User.instance;
      }
      this.name = name
      this.age = age
      this.career = career 
      // 缓存
    User.instance = this;
  }
 const lilith1  = new User('lilith', 23 , 'coder') 
 const lilith  = new User('lilith', 23 , 'coder')
 lilith1   ===  lilith  //true

当然我们也可以使用es6的class类实现单例模式

class User {
  constructor(name , age, career){
      this.name = name
      this.age = age
      this.career = career 
  }
  static getInstance(name , age, career) {
        // 判断是否已经new过1个实例
        if (!User.instance) {
            // 若这个唯一的实例不存在,那么先创建它
            User.instance = new User(name , age, career)
        }
        // 如果这个唯一的实例已经存在,则直接返回
        return User.instance
    }
}
const s1 = SingleDog.getInstance('lilith', 23 , 'coder')
const s2 = SingleDog.getInstance('lilith', 23 , 'coder')

// true
s1 === s2

可以看出,在getInstance方法的判断和拦截下,我们不管调用多少次,User 都只会给我们返回一个实例,s1和s2现在都指向这个唯一的实例。当然我们也可以使用闭包去实现单例模式。

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

单例模式的实际应用

再vuex里面的使用

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

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

通过调用Vue.use()方法,我们安装了 Vuex 插件。Vuex 插件是一个对象,它在内部实现了一个 install 方法,这个方法会在插件安装时被调用,从而把 Store 注入到Vue实例里去。也就是说每 install 一次,都会尝试给 Vue 实例注入一个 Store。
在 install 方法里,有一段逻辑和我们楼上的 getInstance 非常相似的逻辑:

et 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的钩子函数里
  applyMixin(Vue)
}

楼上便是 Vuex 源码中单例模式的实现办法了,套路可以说和我们的getInstance如出一辙。通过这种方式,可以保证一个 Vue 实例(即一个 Vue 应用)只会被 install 一次 Vuex 插件,所以每个 Vue 实例只会拥有一个全局的 Store。
我们平常做登录框的时候就是要求多次点击按钮只会创建一个登录框

import Vue from 'vue'
import loginPopupComponent from './login.vue'
import i18n from '@/i18n/index.js'
 
const getSingle = function(fn) {
  var result
  return function() {
    return result || (result = fn.apply(this, arguments))
  }
}
 
const Login = {
    Vue.prototype.$loginPopup = getSingle(function(options) {
      const LoginConstructor = Vue.extend(loginPopupComponent)
      const div = document.createElement('div')
      document.body.appendChild(div)
      const vm = new LoginConstructor({
        propsData: options,
        i18n
      }).$mount(div)
      vm.innerShowPopup = true
      return vm
    })
  }
 
}
 
export default Login

参考掘金小册《JavaScript 设计模式核⼼原理与应⽤实践》

posted @ 2021-11-03 08:38  自在一方  阅读(289)  评论(0编辑  收藏  举报