前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JS 设计模式之单例模式(创建型)

JS 设计模式之单例模式(创建型)

作者头像
Leophen
发布2021-06-21 20:25:15
6010
发布2021-06-21 20:25:15
举报
文章被收录于专栏:Web前端开发Web前端开发

单例模式——类仅有一个实例

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

1、单例模式的实现思路

如何才能保证一个类仅有一个实例?

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

class SingleDog {
  show() {
    console.log('我是一个单例对象')
  }
}

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

console.log(s1 === s2)  // false

这里 new 了一个 s1,又 new 了一个 s2,很明显 s1 和 s2 之间没有任何瓜葛,两者是相互独立的对象,各占一块内存空间。

而单例模式想要做到的是,不管我们尝试去创建多少次,它都只给你返回第一次所创建的那唯一的一个实例。

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

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

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

console.log(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 都只会给我们返回一个实例,s1 和 s2 现在都指向这个唯一的实例。

2、单例模式的应用

① 实现一个 Storage

描述:

实现 Storage,使得该对象为单例,基于 localStorage 进行封装。实现方法 setItem(key,value) 和 getItem(key)。

实现(静态方法版):

// 定义Storage
class Storage {
  static getInstance() {
    // 判断是否已经 new 过 1 个实例
    if (!Storage.instance) {
      // 若这个唯一的实例不存在,那么先创建它
      Storage.instance = new Storage()
    }
    // 如果这个唯一的实例已经存在,则直接返回
    return Storage.instance
  }
  getItem(key) {
    return localStorage.getItem(key)
  }
  setItem(key, value) {
    return localStorage.setItem(key, value)
  }
}

const storage1 = Storage.getInstance()
const storage2 = Storage.getInstance()

storage1.setItem('name', '李雷')
// 李雷
storage1.getItem('name')
// 也是李雷
storage2.getItem('name')

console.log(storage1 === storage2)  // true

实现(闭包版):

// 先实现一个基础的 StorageBase 类,把 getItem 和 setItem 方法放在它的原型链上
function StorageBase() { }
StorageBase.prototype.getItem = function (key) {
  return localStorage.getItem(key)
}
StorageBase.prototype.setItem = function (key, value) {
  return localStorage.setItem(key, value)
}

// 以闭包的形式创建一个引用自由变量的构造函数
const Storage = (function () {
  let instance = null
  return function () {
    // 判断自由变量是否为 null
    if (!instance) {
      // 如果为 null 则 new 出唯一实例
      instance = new StorageBase()
    }
    return instance
  }
})()

// 这里其实不用 new Storage 的形式调用,直接 Storage() 也会有一样的效果
const storage1 = new Storage()
const storage2 = new Storage()

storage1.setItem('name', '李雷')
// 李雷
storage1.getItem('name')
// 也是李雷
storage2.getItem('name')

console.log(storage1 === storage2)  // true
② 实现一个全局唯一的模态框(Modal 弹框)

思路: 万变不离其踪,getInstance 方法、instance 变量、闭包和静态方法

实现:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>单例模式弹框</title>
</head>
<style>
  #modal {
    height: 200px;
    width: 200px;
    line-height: 200px;
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    border: 1px solid black;
    text-align: center;
  }
</style>

<body>
  <button id='open'>打开弹框</button>
  <button id='close'>关闭弹框</button>
</body>
<script>
  // 核心逻辑,这里采用了闭包思路来实现单例模式
  const Modal = (function () {
    let modal = null
    return function () {
      if (!modal) {
        modal = document.createElement('div')
        modal.innerHTML = '我是一个全局唯一的Modal'
        modal.id = 'modal'
        modal.style.display = 'none'
        document.body.appendChild(modal)
      }
      return modal
    }
  })()

  // 点击打开按钮展示模态框
  document.getElementById('open').addEventListener('click', function () {
    // 未点击则不创建 modal 实例,避免不必要的内存占用;此处不用 new Modal 的形式调用也可以,和 Storage 同理
    const modal = new Modal()
    modal.style.display = 'block'
  })

  // 点击关闭按钮隐藏模态框
  document.getElementById('close').addEventListener('click', function () {
    const modal = new Modal()
    if (modal) {
      modal.style.display = 'none'
    }
  })
</script>

</html>

3、生产实践——Vuex 中的单例模式

① 理解 Vuex 中的 Store

Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。 至此它便作为一个“唯一数据源 (SSOT)”而存在。 这也意味着,每个应用将仅仅包含一个 store 实例。

单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 ——Vuex 官方文档

在 Vue 中,组件之间是独立的,组件间通信最常用的办法是 props(限于父组件和子组件之间的通信),稍微复杂一点的(比如兄弟组件间的通信)我们通过自己实现简单的事件监听函数也能解决掉。

但当组件非常多、组件间关系复杂、且嵌套层级很深的时候,这种原始的通信方式会使我们的逻辑变得复杂难以维护。

这时最好的做法是将共享的数据抽出来、放在全局,供组件们按照一定的的规则去存取数据,保证状态以一种可预测的方式发生变化。 于是便有了 Vuex,这个用来存放共享数据的唯一数据源,就是 Store。

② Vuex 如何确保 Store 的唯一性
// 安装 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 的钩子函数里
  applyMixin(Vue)
}

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

4、小结

通过上面的源码解析可以看出,每次 install 都会为 Vue 实例初始化一个 Store。 假如 install 里没有单例模式的逻辑,那么在一个应用里不小心多次安装了插件:

// 在主文件里安装 Vuex
Vue.use(Vuex)

...(中间添加 / 修改了一些store的数据)

// 在后续的逻辑里不小心又安装了一次
Vue.use(Vuex)

失去了单例判断能力的 install 方法,会为当前的 Vue 实例重新注入一个新的 Store,也就是说你中间的那些数据操作全都没了,一切归 0。 因此,单例模式在此处是非常必要的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-03-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例模式——类仅有一个实例
    • 1、单例模式的实现思路
      • 2、单例模式的应用
        • ① 实现一个 Storage
        • ② 实现一个全局唯一的模态框(Modal 弹框)
      • 3、生产实践——Vuex 中的单例模式
        • ① 理解 Vuex 中的 Store
        • ② Vuex 如何确保 Store 的唯一性
      • 4、小结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档