一 什么是单例模式
单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用
二 实现一个单例模式
2.1 ES6的class写法
class Singleton {
static instance = null
constructor() {
this.num = Math.random()
}
getNum() {
return this.num
}
static getInstance() {
if(!Singleton.instance) {
Singleton.instance = new Singleton()
}
return Singleton.instance
}
}
let singleA = Singleton.getInstance()
let singleB = Singleton.getInstance()
console.log(singleA.getNum() === singleB.getNum()) //true
上面的Singleton.getInstance()永远获取到的都是同一个实例,他们初始化的num的值一定都是相同的,所以两个单例通过getNum()方法获取的值也是相等的.但是单例模式下初始值都是固定的,有的时候我们需要改变num的初始值,那么可以做如下改变:
class Singleton {
static instance = null
constructor() {
this.num = Math.random()
}
getNum() {
return this.num
}
setNum(val) {
this.num = val
//返回的this指向当前的单例
return this
}
static getInstance() {
if(!Singleton.instance) {
Singleton.instance = new Singleton()
}
return Singleton.instance
}
}
let singleA = Singleton.getInstance()
let singleB = Singleton.getInstance()
//改变singleB单例的初始值
let numB = singleB.setNum(10).getNum()
let numA = singleA.getNum()
console.log(numA,numB) // 10 10
虽然改变了初始值,但是还是会影响到创建的多个单例上singleA,singleB.
2.1.1 语法缺陷
let singleA = Singleton.getInstance()
//修改了这个单例
Singleton.instance = null
let singleB = Singleton.getInstance()
console.log(singleA.getNum() === singleB.getNum()) //false
这里我们修改了Singleton.instance的值造成后面生成的单例singleB与singleA不在是同意了单例了,为了解决这个问题,我们利用Symbol来实现:
const instance = Symbol.for('instance');
class Singleton {
static [instance] = null
.......
static getInstance() {
if(!Singleton[instance]) {
Singleton[instance] = new Singleton()
}
return Singleton[instance]
}
}
let singleA = Singleton.getInstance()
let singleB = Singleton.getInstance()
console.log(singleA.getNum() === singleB.getNum()) //true
这样就可以避免instance被误写,但是还是可以被改写:
Singleton[instance] = null
let singleB = Singleton.getInstance()
console.log(singleA.getNum() === singleB.getNum()) //false
如果键名使用Symbol方法生成,那么外部将无法引用这个值,当然也就无法改写:
const instance = Symbol.for('instance');
class Singleton {
XXXXX(后面代码一样)
}
其中的区别是:
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”)30 次,每次都会返回同一个 Symbol 值,但是调用Symbol(“cat”)30 次,会返回 30 个不同的 Symbol 值。
这里每次Singleton.getInstance()都会重新创建一个新的symbol的instance,所以每次都不同
2.2 ES5的写法:
var MySingleton = (function() {
let instance = null
function Singleton() {
this.num = Math.random()
}
Singleton.prototype.getNum = function () {
return this.num
}
return {
getInstance: function() {
if(!instance) {
instance = new Singleton()
}
return instance
}
}
})()
let singleA = MySingleton.getInstance()
let singleB = MySingleton.getInstance()
//改变singleB单例的初始值
let numB = singleB.getNum()
let numA = singleA.getNum()
console.log(numA === numB) //true
三 总结
1. 单例模式,永远指向的都是同一个实例,改变其属性值,会影响到所有实例
2. 单例模式的优势: 因为只创了一个实例,这样大大节省了空间
3. 单例模式的应用场景:
3.1 多次点击同一个弹窗
3.2 下载多份材料的时候
需要注意的是: 单例模式确实节省了很多空间,但是也意味着你将多种不同的情况耦合在了一起,这样增大程序出错的机会,也不方便维护,更难测试