什么是单例模式?
单例模式的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
简单来说,单例模式就是把一堆代码,放入到一个逻辑单元里面,可以通过单一的变量来访问。最大的好处就是封装代码,减少全局变量。
为什么要使用单例模式?
在我们最开始的写法中。我们通常会这样写:
var btn = document.querySelector('#btn')
btn.onclick = function() {
render()
}
function render() {
console.log('render')
}
那么上面的代码有什么问题呢?
最主要的就是全局变量多,难以维护。
现在我们开始改进:
我们先把这一堆代码整合到一个对象里面,作为他的属性和方法,只保留一个全局变量。通过预留的入口启动。
var app = {
btn: document.querySelector('#btn'),
init: function() {
this.bind()
},
bind: function() {
var _this = this
this.btn.onclick = function() {
_this.render()
}
},
render() {
console.log('render jirengu.com')
}
}
app.init()
虽然全局变量可以访问了,但是还是有一个缺点,app里面的属性和方法还是暴露了出来,可以直接用app访问。那我们接下来使用闭包来将部分变量和方法隐藏起来。
var app = (function(){
var btn = document.querySelector('#btn')
function bind() {
btn.onclick = function() {
render()
}
}
function render() {
console.log('render jirengu.com')
}
return {
init: function() {
bind()
}
}
})()
app.init()
这种特殊的单例模式也叫模块模式(module pattern)。
那么还有没有改进的方法呢?当然还有,那就是在我们需要的时候在去创建,而不是立即创建实例。代码如下:
var Singleton = (function(){
var instance
function createInstance() {
var btn = document.querySelector('#btn')
function bind() {
btn.onclick = function() {
render()
}
}
function render() {
console.log('render jirengu.com')
}
return {
init: function() {
bind()
}
}
}
return {
getInstance: function() {
if(!instance) {
instance = createInstance()
}
return instance
}
}
})()
var app = Singleton.getInstance()
app.init()
单例模式的应用场景
当我们访问一个网站的时候,比如访问以前的wepQQ(老版本),当我们点击时候,会有一个弹出层,来让我们去登陆。如下图:
那么,这个唯一的窗口要如何创建呢?
由于我们知道只有当点击登陆按钮的时候,弹层才会出现,所以,我们可以在点击按钮后,惰性的创建一个div弹层。所以js代码如下:
var createLoginLayer = function(){
var div = document.createElement( 'div' );
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild( div );
return div;};
document.getElementById( 'loginBtn' ).onclick = function(){
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
可是这样的实现方法有一个缺点就是,当我们每次点击登陆按钮时,都会创建一个新的DIV,当我们点击关闭按钮时候,这个浮层又会消失。这样频繁的触发DOM操作,创建和删除节点明显是不合理的,也是不必要的。
那么我们是否可以设置一个变量来判断是否已经创建过浮层呢?改进后代码如下:
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( 'loginBtn' ).onclick = function(){
var loginLayer = createLoginLayer();
loginLayer.style.display = 'block';
};
这样便实现了只创建一个浮层需求。
可是上面的代码还有一个问题:假如我还有其他的地方要创建单一的浮层,那么,我们要几乎将代码原封不动的写一遍,然后实现这个需求。
那么,我们需要把创建一个div和其他的iframe的公共部分给抽离出来:用一个变量来标志是否创建过对象,如果是,则在下次直接返回这个已经创建好的对象 。
var obj;
if (!obj){
obj = {};
}
那么我们将这些方法抽离出来,这些逻辑被封装在getSingle 函数内部,创建对象的方法 fn 被当成参数动态传入 getSingle 函数,代码如下:
function getSingle(fn){
var result;
return result || (result = fn.call(this,arguments));
}
我们抽离代码后,便可以将创建的逻辑单独拿出来,放入getSingle,我们用result来保存fn的值,由于闭包,所以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( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
};
总结
首先单例模式的核心是:确保只有一个实例,并提供全局访问。
单例模式是一个简单而且使用的模式,而且惰性的单例模式,是我们可以完成需要的时候再去请求的功能,并且只创建唯一一个。
用好单例模型可以减少DOM的重复渲染,提高浏览器运行的效率。
参考文献
JavaScript设计模式与开发实践