单例模式
只有一个实例且全局可以访问。
面向对象的单例模式
在面向对象语言中,我们创建一个对象是通过类,因此我们创建一个单例也是基于类进行改造
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);
|
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);
|
封装变化的部分
通过闭包实现的单例更好的利用了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)
|
小结
在单例模式中,除了考虑如何创建单例使得全局唯一,还需要考虑如何更好地管理单例,对于每个函数职责的划分将贯穿整个设计模式。