什么是单例模式
单例模式(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 变量,三下五除二即可实现。
完整代码
<!DOCTYPE html>
<html lang="en">
<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>单例模式全局Loading</title>
<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 instance
return 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 这种需要全局唯一状态的时候,就是单例模式登场的时候。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。