定义
保证一个类仅有一个实例,并提供一个访问他的全局方法
优缺点
优点
- 资源管理: 确保程序只有一个实例,减少资源消耗和内存占用
- 全局访问:单例模式提供了全局唯一访问方式,使得程序在任何地方都能轻松的访问到此实例
缺点
- 全局访问:这个特点既是他的优点也是他的缺点,使用不当会导致程序意外发生
- 耦合性高:对象的创建和使用紧密地耦合在一起,可能导致其他模块对单例对象的依赖增加,降低了系统的可维护性和可扩展性
应用场景
全局缓存(如localforage实现,他是通过class实现,但是导出的是他的实例)、浏览器中的window、登录验证弹窗等
实现
饿汉式(指实例在类创建的时候就已经创建)
class SingletonPattern{
// 私有变量,存放实例
static #instance = new SingletonPattern()
constrctor(){}
// 公共方法访问实例
static getInstance(){
return this.#instance
}
}
const ins1 = SingletonPattern.getInstance()
const ins2 = SingletonPattern.getInstance()
console.log(ins1 === ins2) // true
懒汉式(惰性单例,在真正使用的时候才去创建实例)
class SingletonPattern{
// 私有变量,存放实例
static #instance
constrctor(){}
// 公共方法访问实例
static getInstance(){
if (!this.#instance){
this.#instance = new SingletonPattern()
}
return this.#instance
}
}
const ins1 = SingletonPattern.getInstance()
const ins2 = SingletonPattern.getInstance()
console.log(ins1 === ins2) // true
优化
当前的单例模式是不透明的,使用方必须要知道当前类是单例类,不能像一般使用new创建实例,有没有一种方法可以既能让他像传统使用一样,又让他是单例。答案是有的,通过代理来实现单例模式,将创建和使用对象隔离开来。
// 业务类,具体业务的实现
class A {
options
constrctor(options){
this.options = options
}
}
// 代理类 这里需要使用函数的方式而不是class方式创建类
const ProxySingletonA = (function(Constructor){
let _instance
return function (...args){
if (!_instance){
_instance = new Constructor(args)
}
return instance
}
})(A)
const a = new ProxySingleA('a')
const b = new ProxySingleA('b')
consle.log(a===b) // true
在这种实现方式里面我们通过一个代理类实现了,通过new来实现单例模式,并且将业务和管理单例分开了,从而获得更好的维护和扩展
JavaScript里的单例
对于JavaScript来说单例模式可以说是随处可见,不一定就是传统的这种实现方式。
单例模式的核心是确保只有一个实例,并提供全局访问。
这给了我们启发,全局对象是不是也可以当做单例模式来使用如:var a = {}通过这种方式创建对象,对象确实是唯一的,如果它被声明在全局作用域中,这就满足了单例模式的两个基本条件。不过全局变量谨慎使用,可以使用命名空间,闭包等来规避。
ESModule单例
现如今我们更多的使用ESModule来编写我们的代码,那么单例模式也可以是这样的:
// SingletonPattern.js
class SingletonPattern{
constrctor(){}
// 以下是业务代码
}
export default new SingletonPattern()
这个SingletonPattern.js文件对外暴露的就是SingletonPattern的一个实例,我们在其他文件引入的时候始终引入的都是这个实例,也就满足单例的特性。这种方法在一些库中很常见,如:localforage,在我们自己编写一些库的时候也可以采用这种方式。
参考文献
《Javascript设计模式与开发实践》