发布于 

设计模式之单例模式

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。也可以这样认为,单例只是全局的一个别称。

透明的单例模式

透明的单例模式:下面,我们将使用CreateDiv单例类,它的作用是负责在页面中创建唯一的div节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CreateDiv {

private static instance:CreateDiv
//设置constructor为private,即保护类外部不能直接new一个实例
private constructor(public html:string){
//创建div
this.init()
}
//定义一个static方法,static的作用是把这个方法直接挂载到类上,
//可以直接通过CreateDiv.createDivInstance()来调用,而无需new一个实例对象
public static createDivInstance(){
//在createDivInstance中进行判断,如果this.instance为undefined,就new一个实例,
//否则直接返回,这就保证这个CreateDiv类只有一个实例
if(!this.instance){
this.instance = new CreateDiv('a div')
}
return this.instance
}
//内部创建div方法
private init(){
const div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}

}

let a = CreateDiv.createDivInstance()
let b = CreateDiv.createDivInstance()
console.log(a===b) //控制台输出:true

代理单例模式

在上段代码中,CreateDiv类实际上负责了两件事情。
第一:创建对象和初始化init方法;第二:保证只有一个对象
这不符合“单一职责原则”的概念,假如我们某天需要利用这个类,在页面中创建千千万万的div,即要让这个类从单例类变成一个普通的可产生多个实例的类,那我们必须得改写CreateDiv构造函数,把控制创建唯一对象的那一段去掉,这种修改会给我们带来不必要的烦恼。
现在我们通过引入代理类的方式,来解决上面提到的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CreateDiv {

constructor(public html: string) {
this.init()
}
private init() {
let div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}

}
//引入代理类proxySingletonCreateDiv
class ProxySingletonCreateDiv {

private static instance: CreateDiv

public static getInstance(text: string) {
if (!this.instance) {
this.instance = new CreateDiv(text)
}
return this.instance
}
}

//CreateDiv 就变成了一个普通的类,它跟 proxySingletonCreateDiv 组合起来可以达到单例模式的效果。

const a = ProxySingletonCreateDiv.getInstance('diva')
const b = ProxySingletonCreateDiv.getInstance('divb')
console.log(a == b) //控制台输出true

惰性单例

惰性单例:是在需要的时候才创建对象实例。

实际上在文章开头就使用过这种技术,instance实例对象总是在我们调用CreateDiv.createDivInstance时候才被创建,上述都是基于”类“的单例模式,这在javascript中并不适用,我们应该使用函数+闭包的方式实现惰性单例。

下面以网页中的登录弹窗为例:
首先在用户进入网页时:如果登录弹窗一开始就被创建好,在用户只浏览不进行登录的情况下,这样会白白浪费一些DOM节点,只有用户点击登录按钮时才开始创建该弹窗;

还有就是该弹窗在页面是唯一的,不可能出现同时存在两个登录窗口的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//<button id="loginBtn">登录</button>
let createLoginLayer = (function(){
let div:HTMLDivElement;
return function(){
if(!div){
div=document.createElement('div')
div.innerHTML = '我是登录弹窗'
div.style.display = 'none'
document.body.appendChild(div)
}
return div
}
})()

let btn = document.getElementById('loginBtn')
btn?.addEventListener('click',()=>{
let loginLayer = createLoginLayer();
loginLayer.style.display = 'block'
})