单例模式在前边的闭包中有实现过一部分。它的核心是确保只有一个实例,并且提供全局访问点。单例模式的实现主要是利用闭包来保存实例,如果实例已经存在,就使用该实例,如果不存在,则执行相应的函数来创建实例。
一、单例模式在实战中最常见的就是创建弹窗:
常用的创建弹窗的方法主要有以下几种:
1、页面初始化的时候,创建好一个隐藏的弹窗,当用户需要弹窗显示时,再修改样式,让其显示。
<body>
<input type="button" value="打开弹窗" id="open" />
<script>
// 初始化渲染弹窗
var dialogLayer = (function (){
var div = document.createElement('div');
div.innerHTML = '我是弹窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
})();
var oOpen = document.getElementById('open');
// 按钮操作时修改样式
oOpen.onclick = function (){
dialogLayer.style.display = 'block';
}
</script>
</body>
这种方式在页面初始化时,弹窗即被创建好添加到页面中了。但是如果该弹窗不是绝对会被使用的,那这些dom节点可能会被浪费。
2、用户需要弹窗显示时,创建弹窗并显示,隐藏时修改样式把弹窗隐藏。后续再显示隐藏,皆修改样式来控制。这种方式相对上一种,性能上要更加优化。下面这种写法是在上面的代码基础上做了简单的修改:
<body>
<input type="button" value="打开弹窗" id="open" />
<script>
// 创建弹窗
var createDialogLayer = function (){
var div = document.createElement('div');
div.innerHTML = '我是弹窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
var oOpen = document.getElementById('open');
var dialogLayer;
// 按钮操作时创建弹窗并修改样式
oOpen.onclick = function (){
// dialogLayer保证该弹窗是惟一的
if(!dialogLayer){
dialogLayer = createDialogLayer();
}
dialogLayer.style.display = 'block';
}
</script>
</body>
这种写法,引入了变量dialogLayer,是为了保证弹窗的惟一,否则会创建多个弹窗。但是它需要在点击操作的时候,做弹窗惟一性的判断,这在模块封装中是不太合理的。下面参考前边闭包的例子改写一下:
// 创建弹窗模块
var createDialogLayer = (function (){
var div;
return function (){
if(!div){ // 保证实例的单一性
// 创建弹窗
div = document.createElement('div');
div.innerHTML = '我是弹窗';
div.style.display = 'none';
document.body.appendChild(div);
}
return div;
}
})();
var oOpen = document.getElementById('open');
// 按钮操作时创建弹窗并修改样式
oOpen.onclick = function (){
var dialogLayer = createDialogLayer();
dialogLayer.style.display = 'block';
}
这种写法跟闭包中的示例非常相似,在闭包中也提到了另一种可能更常用的写法,也比较了二者的区别,如果不清楚的可以去设计模式的闭包那节再了解一下。
如果要写其他类似的功能模块,完全可以按这个套路写出来。但是这种写法还存在一个问题,那就是创建对象的业务代码,与管理是否是单例的逻辑混在了一起。这里既然说的是设计模式,那就需要把这类问题的解决方案抽象出来,做到职责单一。
// 控制只可创建一个实例
var single = function (fn){
var res; // 存储实例
return function (){
// 如果res有值,则说明该实例已存在,则直接返回;否则执行函数,创建实例。
return res || (res = fn.call(this, arguments));
}
}
// 创建弹窗的业务逻辑
var createDialogLayer = function (){
var div = document.createElement('div');
div.innerHTML = '我是弹窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
var dialogLayer = single(createDialogLayer);
var div1 = dialogLayer();
var div2 = dialogLayer();
console.log(div1 === div2); // true
在这里,把业务代码和管理单例的代码分离开,如果后续要创建其他类似的单例对象,只需编写相应的业务逻辑即可。管理单例的single方法可以作为一个通用的方法,在任何需要的时候调用。
设计原则,理论比较枯燥,这里会穿插在设计模式中,根据实例来理解设计原则:
JS中主要的设计原则有这几种:单一职责原则、里氏替换原则、依赖倒置原则、接口隔离原则、合成复用原则、最少知识原则。
在单例模式中,就提到了单一职责原则。它可以降低单个方法的复杂度,按照职责把对象分解成更小的粒度,有利于代码的复用。但是这也有一些缺点,就是会增加代码编写的复杂度。这个在单例模式中主要体现在业务代码和管理单例代码的拆分上,虽然做到了职责单一,但是增加了代码的复杂度。