定义:保证一个类仅有一个实例,并提供一个访问他的全局访问点。
单例子模式是一种常用的设计模式,有些对象我们往往只需要实现一个,比如线程池、全局缓存、
浏览器中的window对象等。
简单实现单例模式
var Singletion = function(name){
this.name = name;
}
Singletion.prototype.getName = function(){
console.log(this.name)
}
Singletion.getTnstance = (function(){
var install = null;
return function(name){
if(!instance){
instance = new Singletion(name);
}
return instance;
}
});
我们通过Singleton.getInstance来获取Singleton类的唯一对象,但这种方式增加了类的“不透明性”,使用者必须知道Singleton类为一个单例类。 测试这个单例类是可使用的:
var a = singleton.getInstance('sven1');
var b = singleton.getInstance('sven2');
console.log(a === b); //true
这种类型的单例模式意义并不大。
透明的单例模式
var CreateDiv = (function(){
var instance;
var CreateDiv = function(html) {
if(instance){
return instance;
}
this.html = html;
this.init();
return instance = this;
}
CreateDiv.prototype.init = function(){
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
return CreateDiv;
})();
var a = new CreateDiv('aaa');
var b = new CreateDiv('bbb');
console.log(a === b) //ture 页面仅有aaa
它的作用是负责在页面中创建唯一的div节点,但也存在一些问题,这代码中使用了自执行函数和闭包,增加了代码的阅读难度,并且自执行内部的CreateDiv负责了两件事。第一件创建对象的和执行初始化init的方法,的违反了“单一职责原则”。假设我们某天需要利用这个类,创建多个div,我们必须改写CreateDiv构造函数,把控制创建的唯一对象的代码去掉。
代理实现单例模式
我们通过引用代理的方式,来解决上面提到的问题。
var CreateDiv = function(html) {
this.html = html;
this.init();
}
CreateDiv.prototype.init = function(){
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
}
var ProxySingleTonCreateDiv = (function(){
var instance;
return function (html){
if(!instance){
instance = new CreateDiv(html);
}
return instance;
}
})()
var a = new ProxySingleTonCreateDiv('aaa');
var b = new ProxySingleTonCreateDiv('bbb');
console.log(a === b) //true
我们把负责管理单例的逻辑移到了代理类proxySingletonCreateDiv。如果要创建多个div 可以直接通过CreateDiv 方法 不用通过代理。
惰性单例
我们前面的代码都是基于“类”的实现模式,但这在JavaScript中并不适用,下面我们将以登录浮窗为例,介绍与全局变量结合实现惰性的单例。
var createLoginLayer = (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 = createLoginLayer();
loginLayer.style.display = 'block';
}
通用惰性单例
上面我们完成了一个可用的惰性端粒,但还存在一些问题。
- 这段代码违反单一职责原则,创建对象和管理单利的逻辑都放在cereateLoginLayer对象内部。
- 如果我们下次创建其他唯一的页面,例如:inframe,需要把createLoginLayer 函数几乎照抄一遍。 接下来我们把如何管理单利的逻辑从原来的代码中抽离出来,把这些逻辑封装在getSingle函数内部,创建的方法fn被当作参数动态传入getSingle函数:
var getSingle = function(fn){
var result;
return function(){
return result || (result = fn.apply(this.arguments));
}
}
var createLoginlayer = function(){
var div = document.createElement('div');
div.innerHTML = '登陆框';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
var createSingleLoginLayer = getSingle(createLoginlayer);
document.getElementById('btn').onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
}
上面就是抽离后的代码,下面我们再试试创建一个唯一的iframe:
var createSingleIframe = getSingle(function(){
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
return iframe;
});
document.getElementById('btn').onclick = function(){
var iframelayer = createSingleIframe();
iframeLayer.src = 'http://xxxxxx';
}
上面的例子中,我们把创建实例对象的职责和管理单例的职责分别放到了两个方法里,这两个方法相互独立不影响。