JavaScript设计模式笔记-单例模式

定义

单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局放完电

4.1 实现单例模式

要实现一个标准的单例模式并不复杂,无非是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
var Singleton=function(name){
this.name=name;
this.instance=null;//判断是否已经创建类
}
Singleton.prototype.getName=function(){
console.log(this.name)
}
Singleton.getInstance=function(name){
if(!this.instance){
this.instance=new Singleton(name)
}
return this.instance
}
var a=Singleton.getInstance('sven1');
var b=Singleton.getInstance('sven2');
console.log(a===b)//true
————闭包版————
var Singleton=function(name){
this.name=name
}
Singleton.prototype.getName=function(){
console.log(this.name)
}
Singleton.getInstance=function(name){
var instance=null
return function(){
if(!instance){
instance=new Singleton(name)
}
return instance
}
}()

首先创建了一个构造器也就是Singleton其中this.instance是用来判断是否已经构造过一次类,当执行var a=Singleton.getInstance(‘sven1’)的时候 会构造一个类. 然后执行 var b的时候 因为已经构造过 仅仅是返回this.instance而不去重新构造.这就是单例模式.
但是上述单例模式有个问题 不能直接通过new xxx来构造一个单例模式 而必须通过 xxx.func来构造.

4.2 透明的单例模式

比如一个创建createDiv的函数 当创建单例模式只需要new xxx 而不是去xxx.func来构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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('sven1');
var b = new CreateDiv('sven2');
console.log(a===b);//true

这段代码中 createDiv函数实际上负责了两件事,第一件事是创建对象和执行初始化init方法,第二个是保证只有一个对象. 但是这样看起来有点奇怪 并且如果后期需要把单例模式改写为多个实例的类 那么必须的改写createDiv构造函数

4.3 用代理实现代理模式

通过引入代理类的方式来解决上述问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}
//接下来引入代理类proxSingletonCreateDiv
var proxSingletonCreateDiv=function(){
var instance;
return function(html){
if(!instance){
instance=new CreateDiv(html)
}
return instance
}
}();
var a=new proxSingletonCreateDiv('sven1');
var b=new proxSingletonCreateDiv('sven2');
console.log(a===b);//true

这样createDiv只需要负责一件事也就是单一原则. 当函数被显式的返回一个对象 也就是proxSingletonCreateDiv显式的返回了instance 所以最终 a 与 b 返回的是CreateDiv

4.4 JavaScript中的单例模式

JavaScript原本就是一门无class语言,单例模式的核心就是 确保只有一个实例,并且提供全局访问.那么很简单 JavaScript的全局变量就可以实现.

1
var a={}

但是全局变量会造成命名空间污染 而且很容易被覆盖.那么我们可以使用以下的几种方法来降低全局变量带来的命名污染

1.使用命名空间

1
2
3
4
5
6
7
8
var namespace1={
a:function(){
console.log(1);
},
b:function(){
console.log(2);
}
};

把a和b都定义为namespace1的属性 这样就可以减少变量与全局中用于打交道的机会,而且我们还可以动态创建命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var MyApp={};
MyApp.namespace=function(name){
var parts=name.split('.');
var current=MyApp;
for(var i in parts){
if(!current[parts[i]]){
current[parts[i]]={};
}
current=current[parts[i]]
}
}
MyApp.namespace( 'event' );
MyApp.namespace( 'dom.style' );
console.dir( MyApp );
// 上述代码等价于:
var MyApp = {
event: {},
dom: {
style: {}
}
};

2.使用闭包封装私有变量

这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信

1
2
3
4
5
6
7
8
9
var user=(function(){
var __name='sven',
__age=29;
return {
getUserInfo:function(){
return __name+'-'+__age
}
}
})()

4.5 惰性单例

如果我们全局都用不到这个单例模式.但是我们初始化又加载了这个单例模式 这就会造成一个内存开销.
在JavaScript中基于类的单例模式并不适用 那么下面来介绍与全局变量结合实现惰性的单例.

1
2
3
4
5
6
7
8
9
10
var loginLayer=(function(){
var div=document.createElement('div');
div.innerHtml='我是登陆悬浮窗';
div.style.display='none';
document.body.appendChild(div);
return div
})()
document.getElementById('loginBtn').onclick=funciton(){
loginLayer.style.display='none'
}

这个单例在初始化就被创建了,如果我们仅仅是进去浏览并不登陆.这个单例就被浪费了.
下面来写惰性加载的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var createLoginLayer=(function(){
var div;
return function(){
if(!div){
var div=document.createElement('div');
div.innerHtml='我是登陆悬浮窗';
div.style.display='none';
document.body.appendChild(div);
}
return div
}
})()
document.getElementById('loginBtn').onclick=funciton(){
var loginLayer=createLoginLayer()
loginLayer.style.display='none'
}

这样就做到了惰性用需加载 只有需要的时候才加载惰性单例

4.6 通用的惰性单例

但是上述的代码仍旧违反了一个原则 也就是单一职责的原则 创建对象和管理单例的逻辑都放在createLoginLayer里面.如果下次我们需要createScript得如法炮制一遍.那么我们可以把不变的抽出来也就是引入一个代理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var getSingle=function(fn){
var Single;
return function(){
return Single||(Single=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( 'loginBtn' ).onclick = function(){
var loginLayer = createSingleLoginLayer();
console.log(216,loginLayer)
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();
console.log(227,loginLayer)
loginLayer.src = 'http://baidu.com';
};

当调用getSingle函数的时候 他会返回一个闭包 并且会检测是否存在了单例 如果存在就返回存在的单例 否则则创建单例.
比如jquery的one只需要绑定一次click事件

1
2
3
4
5
6
7
8
9
10
11
12
13
var bindEvent = function(){
$( 'div' ).one( 'click', function(){
alert ( 'click' );
});
};
var render = function(){
console.log( '开始渲染列表' );
bindEvent();
};
render();
render();
render();

用上述的单例模式也可以实现

1
2
3
4
5
6
7
8
9
10
11
12
13
var bindEvent = getSingle(function(){
document.getElementById( 'div1' ).onclick = function(){
alert ( 'click' );
}
return true;
});
var render = function(){
console.log( '开始渲染列表' );
bindEvent();
};
render();
render();
render();

# 小结
论闭包和高阶函数的重要性.单例模式最重要的在于 创建对象和管理单例的职责被分布在两个不同的方法中 这才是单例模式的威力。