最近在重新修炼js的设计模式,发现平时自己所写的代码,无意中就使用到了某种的设计模式,所以特意记录一下,以便以后自己查看。
一.单例模式
单例模式指的是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式,是一种很常见的模式,至少在我现在工作中经常用到。单例模式所强调的就是,有且仅有一个对象,并且这个对象是全局变量。那么,它的使用场景主要集中在登陆弹窗,内容提示框,loading加载组件等。
二.实现单例模式
想要实现一个标准的单例模式并不复杂,无非就是用一个变量标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。
var Singleton = function(name) {
// 定义一个构造函数
this.name = name;
this.instance = null;
};
Singleton.prototype.getName = function() {
// 原型上添加getName方法
console.log(this.name);
};
Singleton.getInstance = function(name) {
if (!this.instance) {
// 如果不存在对象,则实例化Singleton
this.instance = new Singleton(name);
}
return this.instance;
};
var a = Singleton.getInstance('kafei');
var b = Singleton.getInstance('lvcha');
console.log(a === b); // true 同一个对象
三.惰性单例
惰性单例指的是在需要的时候才创建对象实例。假设我们需要做一个唯一的弹窗。
第一种方案是在页面加载完成的时候便创建好这个div弹窗,这个弹窗一开始肯定是隐藏的状态,当用户点击登陆按钮的时候,它才会显示:
var loginLayer = (function () {
var div = document.createElement("div");
div.innerHTML = "登陆弹窗";
div.style.display = "none";
document.body.appendChild(div);
return div;
})()
document.getElementById("btn").onclick = function () {
loginLayer.style.display = "block";
}
但是这种方式,存在问题,因为页面一旦加载,那么他就已经创建好了节点。如果我不希望它一开始就追加节点,而是用户点击按钮的时候,才创建这个节点。所以,需要改造一下代码:
var loginLayer = function () {
var div = document.createElement("div");
div.innerHTML = "登陆弹窗";
div.style.display = "none";
document.body.appendChild(div);
return div;
}
document.getElementById("btn").onclick = function () {
var loginLayer = loginLayer();
loginLayer.style.display = "block";
}
虽然现在已经达到了惰性的目的,但失去了单例的效果。但我们每次点击按钮的时候,都会新创建一个新的div,这显然是一个不合理的情况。
那么,我们可以使用一个变量来判断是否已经创建过了div节点。
var createLayer = (function () {
var div;
return function () {
if (!div) {
div = document.createElement("div");
div.innerHTML = "登陆弹窗";
div.style.display = "none";
document.body.appendChild(div);
}
return div;
}
})()
document.getElementById("btn").onclick = function () {
var loginLayer = createLayer();
loginLayer.style.display = "block";
}
四.通用的惰性单例
虽然上面我们已经完成了一个惰性的单例模式,但还是会存在以下问题:
上面代码违反了单一职责原则,创建对象和管理单例的逻辑都在了createLayer 对象内容。
如果我们下次需要创建页面中唯一的iframe,则必须如法炮制,把createLayer函数再抄一遍,这显然是不合理的。
var createIframe = (function () {
var iframe;
return function () {
if (!iframe) {
iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
}
return iframe;
}
})()
现在我们需要不变的部分抽离出来,不去考虑它创建的是div还是一个iframe,创建唯一一个对象的逻辑是一致的,用一个对象来标志是否创建过对象,如果是则在下次返回这个创建好的对象:
var obj;
if (!obj) {
obj = xxx;
}
那么就编写一个getSingle函数,这个函数接受一个函数(fn)作为参数,而这个fn这是创建对象的一个方法,然后再利用函数闭包中变量不会被销毁的特性,来判断这个对象是否存在。
var getSingel = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments)); // 保持this不变和能够接受参数
}
}
var loginLayer = function (ele, txt) {
return function () {
var oEle = document.createElement(ele);
if (txt) {
oEle.innerHTML = txt;
}
oEle.style.display = "none";
document.body.appendChild(oEle);
return oEle;
}
}
var getSingel = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments)); // 保持this不变和能够接受参数
}
}
var createSingleLayer = getSingel(loginLayer("div", "div"));
// var createIfame = getSingel(loginLayer("iframe", "iframe"));
document.getElementById("btn").onclick = function () {
var layer = createSingleLayer();
layer.style.display = "block";
}
在这个例子中,我们把创建对象的职责和管理单例的职责放在两个不同的函数中,这两个函数相互独立而互不影响,当它们连接在一起的时候,就完成了创建唯一实例对象的功能。这个不得不感叹js的强大。