惰性单例 惰性单例指的是在需要的时候才被创建对象实例,就是需要的时候才动态调用,而不是页面加载好的时候就创建; 例如页面中的弹窗,页面中是唯一的,不可能出现一模一样的弹窗; 第一种解决方法是在页面加载完成的时候便创建好这个div弹窗,不过是隐藏状态
当用户点击登录按钮的时候,它才显示;
var loginLayer = (function(){
var div = document.createElement( 'div' );
div.innerHTML = '活动规则弹窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
})();
document.getElementById('loginBtn').onclick = function(){
loginLayer.style.display = 'block';
}
不过这种方式有个问题,我也许只是浏览下页面,不需要看弹窗内容,但是弹窗总是一开始就被创建好, 那么,很有可能会白白浪费一些DOM节点; 现在改写下代码,用户点击按钮的时候才开始创建改弹窗;
var createLoginLayer = function(){
var div = document.createElement( 'div' );
div.innerHTML = '活动规则弹窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;
};
document.getElementById('btn').onclick = function(){
var loginLayer1 = createLoginLayer();
loginLayer1.style.display = 'block';
};
现在虽然达到了惰性的目的,但失去了单例的效果,因为每次点击都会创建,关闭按钮的时候在删除; 这样频繁的创建和删除节点明显是不合理的. 我们可以通过一个变量来判断是否已经创建过登录浮窗.
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 loginLayer1 = createLoginLayer();
loginLayer1.style.display = 'block';
};
上面已经基本完成; 但是,不难发现这段代码违反单一职责原则,创建对象和管理单例的逻辑都放在createLoginLayer对象内部 如果,下次需要创建页面中唯一的iframe,或者script标签,用来跨域请求数据,就得如法炮制,把createLoginLayer几乎照抄一遍;
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被当成参数动态传入getSingle函数:
var getSingle = function(fn){
var result;
return function (){
return result || (result = fn.apply(this,arguments));
}
};
接下来将用于创建登录浮窗的方法用参数fn的形式传入getSingle,我们不仅可以传入createLoginLayer, 还能出入createScript,createXhr等,之后再让getSingle返回一个新的函数,并且用一个变量resul来 保存fn的计算结果,result变量因为身在闭包中,它永远不会被销毁. 在将来的请求中,如果result已经被赋值,那么它将返回这个值.
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('loginBtn').onclick = function(){
var loginLayer = createSingleIframe();
loginLayer.src = 'http://tunian.com';
}
很明显,我们把创建实例对象的职责和管理单例的职责分别放置在这两个方法里, 这两个方法可以独立变化而不互相影响.当它们连接在一起的时候,完成来创建唯一实例对象的功能.