天天看点

javascript设计模式_JavaScript 设计模式 单例模式

本文原载于 SegmentFault 社区专栏:前端学习 作者: li uxuan

什么是单例模式

单例模式(Singleton Pattern)确保一个类只有一个实例,并提供一个访问它的全局访问点

单例模式是设计模式中较为简单,好理解的一种模式,但是它的使用场景是很广泛的,包括大名鼎鼎的 Vuex 也使用了单例模式。 本文会介绍单例模式的两种实现方法: 类和闭包,同时也会对 Vuex 中的单例模式进行介绍。

实现方法

类 Class 是 ES6 新增的语法,在之前我们想要新建一个对象实例,是通过 new 构造函数的方式来实现的。 我们每次调用 new 的时候,都会生成一个新的实例对象,每个实例对象之间是完全独立的。

function Car (name) {this.name = name;
}var car1 = new Car('benz');var car2 = new Car('audi');console.log(car1 === car2)      // false
           

那么我们来思考一下,怎么才能每次 new 都返回同一个实例对象呢? 肯定是有一个变量将第一次 new 生成的实例对象保存了下来,后面再执行 new 的时候,就直接返回第一次生成的实例对象,这样就实现了单例。 我们通过两种方法来实践一下: 类和闭包。

类实现

class SingletonCar {constructor () {this.name = 'benz';
    }static getInstance () {if (!SingletonCar.instance) {
            SingletonCar.instance = new SingletonCar();
        }return SingletonCar.instance;
    }
}let car1 = SingletonCar.getInstance();let car2 = SingletonCar.getInstance();console.log(car1 === car2)      // true
           

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。 如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。 静态方法可以直接在父类上调用 SingletonCar.getInstance(),而不是在实例对象上调用。 如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 用类来实现单例模式,只要记住这个 getInstance() 静态方法就可以了。

闭包实现

var SingletonCar = (function () {var instance;var SingletonCarTemp = function () {this.name = 'benz';
    };return function () {if (!instance) {
            instance = new SingletonCarTemp();
        }return instance;
    }
})();var car1 = new SingletonCar();var car2 = new SingletonCar();console.log(car1 === car2)      // true
           

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

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() 方法可以安装这个插件,具体 Vue 插件的写法可以参考官方文档 Vuex 在内部实现了一个 install 方法,该方法会在 Vue.use() 时被调用,Vue 会被作为参数传入 install 方法中,从而把 Store 注入到 Vue 中。 但是如果你试过在开发环境多次调用 Vue.use(Vuex) 的话,就会知道是浏览器是会报错的,接下来我们看一下 Vuex 的内部实现。

Vuex 如何保证唯一 Store

上 Vuex 部分源码:

let 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)
}
           

完整源码路径在 vuex/src/store.js 我们可以看到,在 Vuex 内部,先定义了一个变量 Vue,注意这里不是真正的 Vue,只是一个变量,也叫 Vue。 在 Vue.use(Vuex) 的时候,会调用 install 方法,真正的 Vue 会被当做参数传入,如果多次执行 Vue.use(Vuex),也只会生效一次,也就是只会执行一次 applyMixin(Vue),所以只会有一份唯一的 Store,这就是 Vuex 中单例模式的实现。

练习

实现一个全局唯一的 Loading 遮罩。

思路

我们在业务开发的过程中,有很多需求都会有 Loading 状态,这时候直接掏出单例模式,记住上面的 getInstance 静态方法或者闭包 instance 变量,三下五除二即可实现。

完整代码

html><html ><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>单例模式全局Loadingtitle><style>.loading {width: 100vw;height: 100vh;line-height: 100vh;position: absolute;top: 0;left: 0;background-color: #000;opacity: .7;color: #fff;text-align: center;
    }style>head><body><button id="startLoading">点击加载button><script>const Loading = (function () {let instancereturn function () {if (!instance) {
          instance = document.createElement('div')
          instance.innerHTML = '加载中...'
          instance.id = 'loading'
          instance.className = 'loading'document.body.appendChild(instance)
        }return instance
      }
    })()document.getElementById('startLoading').addEventListener('click', () => {const loading = new Loading()
    })script>body>html>
           

总结

单例模式,就是确保一个类只有一个实例,如果采用 class 来实现,记住 getInstance 静态方法,如果采用闭包来实现,记住 instance 变量。 当我们在业务开发中,遇到类似于 Vuex 这种需要全局唯一状态的时候,就是单例模式登场的时候。 - END -

javascript设计模式_JavaScript 设计模式 单例模式

继续阅读