什么是单例模式
-
定义:它保证一个类仅有一个实例,并提供一个访问它的全局访问点。
-
单例模式是创建型设计模式的一种。针对全局仅需一个对象的场景,如线程池、全局缓存、window 对象等
-
也许你对单例模式的概念感到模糊或者不清楚,但是其实在日常的开发中你肯定用到过单例模式
全局对象--最简单的单例模式
-
单例模式的特点是 “唯一” 和 “全局访问”,那么我们可以联想到JavaScript中的全局对象,利用ES6的let不允许重复声明的特性,刚好符合这两个特点;是的,全局对象是最简单的单例模式。
let person = { name: "xuxu", getName: function () {} }
person 就是一个单例,因为person 刚好就符合单例模式的两大特点:"唯一"和"可全局访问"。
-
但我们不建议这么实现单例,因为全局对象会有一些弊端:
- 污染命名空间(容易变量名冲突)
- 维护时不容易管控 (搞不好就直接覆盖了)
实现方式
-
我们每次调用new的时候,都会生成一个新的实例对象,每个实例对象之间是完全独立的。
-
所以如果要实现单例,就需要有一个变量将第一次new生成的实例对象保存了下来,后面再执行new的时候,就直接返回第一次生成的实例对象。
-
这里介绍两种实现方式:闭包 和 类
闭包
这里只介绍惰性单例,即:不在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)
}
-
Vuex 在内部实现了一个 install 方法,该方法会在 Vue.use() 时被调用,Vue 会被作为参数传入 install 方法中,从而把 Store 注入到 Vue 中。
-
可以看到,多次执行 Vue.use(),
applyMixin(Vue)
只在第一次的时候执行,所以只会有一份唯一的 Store。这就是 Vuex 中单例模式的实现。
优缺点
优点:适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用。 缺点:不适用动态扩展对象,或需创建多个相似对象的场景。
多线程编程语言中,单例模式会涉及同步锁的问题。而 JavaScript 是单线程的编程语言,暂可忽略该问题。