聊聊JavaScript单例模式
/ / 点击 / 阅读耗时 8 分钟最近趁着公司项目不是很急,抽空看了会JavaScript设计模式相关的资料。主要学习了其中的单例模式,下面就根据学习,分享我所理解的单例模式是咋样的。
什么是单例模式
我所理解的单例模式就是通过构造函数生成一个唯一实例,但是这种说法不太严谨。更官方一点的解释就是:保证一个类仅有唯一实例,并且能够提供一个全局访问点就叫单例模式
一般定义一个类之后,可以通过new调用构造函数生成多个实例对象,比如:
class Person{
static msg(){
console.log('hello world')
}
}
const p1 = new Person()
const p2 = new Person()
p1 === p2 //false
以上定义了一个Person对象,然后通过new分别生成了p1和p2两个实例对象。很显然p1和p2不相等,这是因为p1和p2指向的是两块不同的存储地址。那么单例模式就是无论怎么创建实例,永远返回第一次创建的那个唯一实例。
那么要实现这个方法,就得先判断构造函数是否生成过一个实例,如果生成了实例就返回这个实例,否则创建一个实例。那么一开始思路就是:
class SingleCase(){
static getInstance() {
// 判断是否已经new过1个实例
if (!SingleCase.instance) {
// 若这个唯一的实例不存在,那么先创建它
SingleCase.instance = new SingleDog()
}
// 如果这个唯一的实例已经存在,则直接返回
return SingleCase.instance
}
}
const p1 = new SingleCase.getInstance()
const p2 = new SingleCase.getInstance()
p1 === p2 // true
除此之外还有一个写法,getInstance的逻辑用闭包来实现:
class SingleCase {
static getInstance = (function () {
// 定义自由变量instance,模拟私有变量
let instance = null
return function () {
// 判断自由变量是否为null
if (!instance) {
// 如果为null则new出唯一实例
instance = new SingleCase()
}
return instance
}
})()
}
const p1 = new SingleCase.getInstance()
const p2 = new SingleCase.getInstance()
p1 === p2
另一种实现方法
上面两种方法都是借助函数方法来实现的,我在想有没有一种可能是通过变量来实现?顺着这个想法,尝试着实现下:
class S{
constructor(){
if(!S.instance){
S.instance = new S()
}else{
return S.instance
}
}
}
const p = new S()
// result : Maximum call stack size exceeded
执行之后发现结果并没有到达预期效果,这是因为第一次new的时候判断当前是否有instance变量,发现没有instance则进入赋值操作。这一步很关键的一点是赋值前先执行了new操作,则有又执行了一遍constructor里面的内容。从而导致无限循环,最终栈溢出。
如何解决这个问题,核心思路就是要在new的时候第一次判断下,具体实现如下:
class S {
constructor() {
if (S.status) {
S.status = false
} else {
if (!S.instance) {
S.status = true
return S.instance = new S()
} else {
return S.instance
}
}
}
}
const p1 = new S()
const p1 = new S()
p1 === p2 //true
new S()第一次的时候按顺序执行,判断是否有status,发现没有就进else里面,通过else里面判断是否已经new过实例,发现没有就给一个状态标记。然后再生成一个实例,程序再次走进constructor,发现状态为true。则什么也不做,此时实例已经生成。然后返回instance属性接受这个实例。此时第一次new 操作已经完成。
第二次再new的时候,判断status为false,然后再判断S.instance是否存在,此时已经存在,则最终返回此实例。
单例模式的应用
dialog弹窗是非常适合单例模式的应用,因为弹窗只有一个,可以通过传参生成不同的弹窗实例。刚好最近公司有个项目用到了,就分享一下。
创建弹窗实例//
/**
@param {String} title 弹窗标题
@param {String} content 弹框内容
@param {String} cancelTex 取消文字
@param {Function} cancel 取消执行函数
@param {Function} confirm 确认执行函数
*/
let DialogModal = (function () {
var modal = null, mask = null;
return function ({ title, content, cancelTex, cancel, confirm }) {
if (!modal) {
modal = document.createElement('div')
mask = document.createElement('div')
mask.className = 'mask';
mask.setAttribute('id', 'Mask')
modal.setAttribute('class', 'dialog')
}
modal.innerHTML = `
<p class="modal-title">
${title}
</p>
<div class="modal-content">
${content}
</div>
<div class="modal-footer">
<p>${cancelTex}</p>
<p>去看看</p>
</div>
`
modal.style.display = 'block'
mask.style.display = 'block'
modal.querySelector('#cancel').onclick = function () {
modal.style.display = 'none'
mask.style.display = 'none'
cancel()
};
modal.querySelector('#confirm').onclick = function () {
modal.style.display = 'none'
mask.style.display = 'none'
confirm()
};
document.body.appendChild(mask)
document.body.appendChild(modal)
// return modal
}
})()
// Example
new Modal({
title: '温馨提示',
content: '确定不去看看优惠券么?',
cancelTex: '坚决放弃',
// 取消按钮事件
cancel: function () {
},
// 确认按钮事件
confirm: function () {
}
})