设计模式--单例模式--每天进步一点点

137 阅读5分钟

什么是单例模式

  1. 定义:它保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  2. 单例模式是创建型设计模式的一种。针对全局仅需一个对象的场景,如线程池、全局缓存、window 对象等

  3. 也许你对单例模式的概念感到模糊或者不清楚,但是其实在日常的开发中你肯定用到过单例模式

全局对象--最简单的单例模式

  1. 单例模式的特点是 “唯一” 和 “全局访问”,那么我们可以联想到JavaScript中的全局对象,利用ES6的let不允许重复声明的特性,刚好符合这两个特点;是的,全局对象是最简单的单例模式。

    
    let person = {
      name: "xuxu",
      getName: function () {}
    }
    
    

    person 就是一个单例,因为person 刚好就符合单例模式的两大特点:"唯一"和"可全局访问"。

  2. 但我们不建议这么实现单例,因为全局对象会有一些弊端:

  • 污染命名空间(容易变量名冲突)
  • 维护时不容易管控 (搞不好就直接覆盖了)

实现方式

  1. 我们每次调用new的时候,都会生成一个新的实例对象,每个实例对象之间是完全独立的。

  2. 所以如果要实现单例,就需要有一个变量将第一次new生成的实例对象保存了下来,后面再执行new的时候,就直接返回第一次生成的实例对象。

  3. 这里介绍两种实现方式:闭包

闭包

这里只介绍惰性单例,即:不在js加载时就进行实例化创建, 而是在需要的时候再进行单例的创建。

思路

借助闭包,在内存中保留了 instance 变量,不会被垃圾回收,用来保存唯一的实例,多次调用 new 的时候,只返回第一次创建的实例。

代码


let single = (function () {
  let instance

  let Person = function (name) {
    this.name = name
  }

  return function (name) {
    if (!instance) {
      instance = new Person(name)
    }
    return instance
  }
})()

let p1 = new single('xuxu') // {name: "xuxu"}
let p2 = new single('xx') // {name: "xuxu"}

这里是在第一调用的就实例化这个单例,也可以在js刚加载的时候就实例化,即声明 instance 时就实例化。

类实现

思路

constructor 构造器 和 static 静态方法

  • 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为 静态方法

代码


class Single {
  constructor (name) {
    this.name = name
  }

  static init (name) {
    if (!Single.instance) {
      Single.instance = new Single(name)
    }
    return Single.instance
  }
}

let p1 = Single.init('xuxu') // { name: "xuxu" }
let p2 = Single.init('xx') // { name: "xuxu" }

静态方法可以直接在父类上调用 Single.init('xuxu'),但不能在实例对象上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

适用场景

单例模式的特点,意图解决:维护一个全局实例对象。 所以使用场景有:

  • 引用第三方库(多次引用只会使用一个库引用,如 jQuery)
  • 弹窗(登录框,信息提升框)
  • 购物车 (一个用户只有一个购物车)
  • 全局态管理 store (Vuex / Redux)
  • ......

下面介绍两种单例:引用第三方库Vuex

引用第三方库

项目中引入第三方库时,重复多次加载库文件时,全局只会实例化一个库对象,如 jQuery,lodash,moment ..., 其实它们的实现理念也是单例模式应用的一种:


// 引入代码库 jQuery (库别名)
if (window.jQuery != null) {
  return window.jQuery;    // 直接返回
} else {
  window.jQuery = '...';   // 初始化
}

Vuex中的单例模式

Vuex 是 Vue 的状态管理类库,类似于 Redux 之于 React,其实他们的理念都是来自于 Flux 架构,用一个全局的 Store 存储应用所有的状态,然后提供一些 API 供用户去读取和修改。一看到全局唯一的 Store,就可以想到是单例模式了。

vuex的引用


import Vue from 'vue'
import Vuex form 'vuex'
import store from './store'

Vue.use(Vuex)

new Vue({
  el: '#app',
  store
})

Vuex 是一个插件,通过调用 Vue.use() 方法可以安装这个插件。

Vuex 如何保证唯一 Store

源码:


let Vue

...

// _Vue是真正的Vue
export function install (_Vue) {
  // 是否已经执行过了 Vue.use(Vuex),如果在非生产环境多次执行,则提示错误
  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.use(Vuex),则把传入的 _Vue 赋值给定义的变量 Vue
  Vue = _Vue
  // Vuex 初始化逻辑
  applyMixin(Vue)
}

  1. Vuex 在内部实现了一个 install 方法,该方法会在 Vue.use() 时被调用,Vue 会被作为参数传入 install 方法中,从而把 Store 注入到 Vue 中。

  2. 可以看到,多次执行 Vue.use(),applyMixin(Vue) 只在第一次的时候执行,所以只会有一份唯一的 Store。这就是 Vuex 中单例模式的实现。

优缺点

优点:适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。 缺点:不适用动态扩展对象,或需创建多个相似对象的场景。

多线程编程语言中,单例模式会涉及同步锁的问题。而 JavaScript 是单线程的编程语言,暂可忽略该问题。

参考文献

  1. 「中高级前端面试」手写代码合集(二)
  2. JavaScript 设计模式(一):单例模式
  3. JavaScript设计模式第1篇:单例模式
  4. JavaScript 单例模式