JavaScript设计模式与开发实践(一)单例模式

单例模式

只有一个实例且全局可以访问。

面向对象的单例模式

在面向对象语言中,我们创建一个对象是通过类,因此我们创建一个单例也是基于类进行改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class SingTon {
instance = null;
constructor(type) {
this.type = type;
}
static getInstance() {
if (!SingTon.instance) {
SingTon.instance = new SingTon();
}
return SingTon.instance;
}
}

let a = SingTon.getInstance();
let b = SingTon.getInstance();
console.log(a === b); // true

JavaScript中的单例模式

但是在JavaScript中,他并不是依靠类来创建对象的,因此我们也不必通过先声明一个类的方式来创建一个单例,我们只需要保证单例模式的要求:只有一个实例 + 全局可访问,我们自然的想到了 闭包

同时在JavaScript中的单例可能更多是一个DOM节点,譬如一个Modal或者一个Dialog一个Message组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const SingTon = (function() {
let Modal = null;
return function () {
if (!Modal) {
Modal = document.createElement("div");
Modal.innerHTML = "我是登录浮窗";
Modal.style.display = "none";
document.body.appendChild(Modal);
}
return Modal;
};
})()

const div1 = SingTon();
const div2 = SingTon();
console.log(div1 === div2); // true

封装变化的部分

通过闭包实现的单例更好的利用了JavaScript的特性,但是这段代码有一个问题: 如果现在不是Modal了呢,我可能需要创建别的元素,加入别的逻辑。 如果按照上面的代码,我们只能CV一份然后修改逻辑。

但是我们应该尝试将那些 变化的部分封装起来 ,这也是封装在面向对象语言中存在的意义。

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
31
32
33
34
const SingTon = function (fn) {
let instance = null;
return function () {
return instance || (instance = fn.apply(this, arguments));
};
}

const createModal = SingTon(function () {
const Modal = document.createElement("div");

Modal.innerHTML = "我是登录浮窗";
Modal.style.display = "none";
document.body.appendChild(Modal);

return Modal;
})

const createMessage = SingTon(function () {
const Modal = document.createElement("div");

Modal.innerHTML = "我是登录浮窗";
Modal.style.display = "none";
document.body.appendChild(Modal);

return Modal;
})
const modal1 = createModal()
const modal2 = createModal()

const message1 = createMessage()
const message2 = createMessage()

console.log(modal1 === modal2);
console.log(message1 === message2);

而且这个函数不止可以创建元素的单例,还可以用在其他的地方。如下代码,如果这样写click事件会被绑定三次,导致一次点击会执行三个click的回调。

1
2
3
4
5
6
7
8
9
10
11
const bindEvent = function (number) {
document.querySelector('.root').addEventListener('click', () => {
console.log(number);
})
};
const render = function (number) {
bindEvent(number);
};
render(1)
render(2)
render(3)

通过SingTon函数优化,使得click只会被绑定一次。但是需要注意,放在SingTon中的函数必须有返回值,这个标志着 是否是初次执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const SingTon = function (fn) {
let instance = null;
return function () {
return instance || (instance = fn.apply(this, arguments));
};
}
const bindEvent = SingTon(function (number) {
document.querySelector('.root').addEventListener('click', () => {
console.log(number);
})
return true
})
const render = function (number) {
bindEvent(number);
};
render(1)
render(2)
render(3)

小结

在单例模式中,除了考虑如何创建单例使得全局唯一,还需要考虑如何更好地管理单例,对于每个函数职责的划分将贯穿整个设计模式。


JavaScript设计模式与开发实践(一)单例模式
https://jing-jiu.github.io/jing-jiu/2023/01/04/notebooks/JavaScript设计模式与实践/设计模式(一)/
作者
Jing-Jiu
发布于
2023年1月4日
许可协议