定义
保证一个类仅有一个实例,比提供全局一个访问他的全局访问点;
使用场景举例
如:全局的登录弹窗、单页面全局的状态管理等
不透明的单例模式
class Singleton {
name = null;
instance = null;
getName() {
return this.name;
}
static getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
const a = Singleton.getInstance('demo');
const b = Singleton.getInstance('demo');
console.log(a === b); // true
在这里我们创建了一个静态方法,用来判断是否已经创建了实例,如果创建了则返回之前已经创建的实例, 这样保证了全局只有一个实例。但是这里有个不好的地方--其他使用者必须要通过文档或者是阅读代码后才能知道new Singleton() 是不能创建一个单例的,必须通过Singleton.getInstance方法才能实现。那么接下来我们来解决这个“不透明性”。
透明的单例
这里所谓的透明指的是使用者并不需要额外使用Singleton.getInstance这样一个特殊的静态方法;只需要和普通的创建对象一样直接通过new创建:
const Singleton = (function() {
let instance = null;
return class Singleton {
name = null;
constructor() {
if (instance) {
return instance;
}
instance = this;
}
getName() {
return this.name;
}
}
})();
const a = new Singleton('demo');
const b = new Singleton('demo');
console.log(a === b); // true
这个方案中我们解决了上面的问题:创建单例的对象和普通的对象不一致的问题,达到了所谓的透明。但是现在仍然是不完美的: 即现在这个类的功能不够单一--即创建了一个单例,又实现了一个返回name的功能(在实际的业务中会比这个功能复杂很多)。接下来我们把创建单例的功能抽出来,让它不和具体的业务功能耦合在一起。
通过代理实现单例
class Demo {
name = null;
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const Singleton = (function() {
let instance = {};
return class Singleton {
constructor(name, SomeClass) {
if (!instance[name]) {
instance[name] = new SomeClass(name);
}
return instance[name];
}
}
})();
const a = new Singleton('demo', Demo);
const b = new Singleton('demo', Demo);
const c = new Singleton('demo1', Demo);
console.log(a === b); // true
console.log(a === c); // false
在这里咱们通过代理(为一个对象提供一个代用品或者占位符)的方式实现了具体业务和单例实现的解偶;但是需要大家注意的是这段代码中和上一段代码有个不同,name不一致的时候咱们重新创建了一个实例;这个可以根据不同的需求自己去选择。
单例模式的优点
优点:
- 全局只创建一个对象,节省了内存消耗,保证了某些常见操作或者用户不当操作时候的健壮性
- 可以很轻松的实现信息的共享(如:页面通信),或者保存(如:用户的输入)
缺点:
某些情况下容易导致内存泄漏,如: 在angular中的注入(我现在知识有限,还不知道angular中如果注销一个全局的注入😄), 又比如以下场景下:
class Demo {
name = null;
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const Singleton = (function() {
let instance = {};
return class Singleton {
constructor(name, SomeClass) {
if (!instance[name]) {
console.log('init');
instance[name] = new SomeClass(name);
}
return instance[name];
}
}
})();
let a = new Singleton('demo', Demo);
a = null;
setTimeout(() => {
const b = new Singleton('demo', Demo);
}, 2000);
在这里咱们先创建实例a, 打印了一次init代表咱们之前这个实例并没创建,然后咱们把新创建的实例a设置为null, 我们的本意是想销毁这个实例,然后在2s后再创建一个新的实例,但是实际上这个时候控制台并没有打印init,这说明我们之前的a = null操作并没有释放上一个实例的引用导致这个实例的内存并没有被释放。所以开发者在开发中的时候一定要特别注意这点。