写在最前
为何会有设计模式这样的东西存在,那是由于程序设计的不完美,须要用设计模式来弥补设计上的缺陷,那立马估计会有童鞋问,既然设计的不完美,那就换个完美点的语言,首先,没有绝对完美的语言存在,其次,借鉴下前辈说的话,灵活而简单的语言更能激发人们的创造力,因此生命力旺盛,这也可以解释,近些年来前端发展的如此迅速的缘由吧。
ps一段,自从开始正儿八经深刻学习前端已经有一年多左右了,当时定的一个看书目标就是最初的是dom入门,以后是高三书和犀牛书,截止到如今这三本基本都算看完了,犀牛书后续的一些章节尚未彻底看完,在上个工做的过程当中还算是比较忙的(多是外包项目比较多),中间有准备了面试,如今是在大厂了,发现要学的实在是太多了,本厂的框架一堆一堆的,哎,慢慢来吧,基础是同样的区别是实现的思想。
看,这是我当时列的书单
语言精粹这个比较薄,是常常须要翻看,一下看完也记不牢,设计模式这本是汤姆大叔翻译的,广泛反映有点晦涩,在技术群里有推荐说腾讯大神曾探写的(JavaScript设计模式与开发实践)不错,讲的比较容易理解,就买了一本,看了前面三章,讲js的面向对象,原型以及闭包的,是设计模式的基础,以为还不错。如今就打算根据看书写下设计模式的一系列的博客,水平不高,求指正,求交流。
正文:设计模式--单例模式
单例模式的定义是,保证一个类有且仅有一个实例,并提供一个访问它的全局访问点。
这个模式是经常使用的模式了,若是结合业务来说的话,就应该是固定的功能,每次调用都没什么多大变化的,好比,弹窗的提示,登陆功能的浮窗,
1,实现单例模式
代码以下javascript
var single = function (name) {
this.name = name;
this.instance = null;
};
single.prototype.getName = function (){
alert(this.name);
};
single.getInstance = function (name) {
if( !this.instance ){
this.instance = new single(name);
}
return this.instance;
};
var a = single.getInstance('sev1');
var b = single.getInstance('sev2');
alert( a === b);
咱们经过 single.getInstance 来获取single类的惟一对象,这种方式相对简单,但有一个问题,就是增长了这个类的“不透明性”,single类的使用者必须知道这是个单例类,跟以往经过new XXX的方式获取对象不一样,这里是使用single.getInstance来获取对象。
2,透明的单例模式
咱们如今的目标是实现一个透明的单例类,用户从这个类中建立对象的时候,能够像使用其余任何普通类同样。在下边的例子中,咱们将使用creatDiv单例类,它的做用是负责页面中建立惟一的div节点,代码以下:
var creatDiv = (function () {
var instance;
var creatDiv = function (html) {
if(instance){
return instance;
}
this.html = html;
this.init();
return instance = this;
};
creatDiv.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
return creatDiv;
})();
var a = new creatDiv('sev1');
var b = new creatDiv('sev2');
alert( a === b );
虽然如今完成了一个透明的单例类的编写,但它一样有一些缺点。
为了把instance封装起来,使用了自执行函数和闭包,而且让这个匿名函数返回真正的单例构造方法,这增长了一些程序的复杂度,阅读起来也不很舒服,
观察下如今的构造函数,
var creatDiv = function (html) {
if(instance){
return instance;
}
this.html = html;
this.init();
return instance = this;
};
这个函数负责了两个事情,第一,建立对象并执行初始化函数,第二,保证只有一个对象,虽然尚未接触到 “单一职责原则”的概念,就这个构造函数来讲,这是一种很差的作法。
假设咱们某天须要利用这个类,在页面中建立千千万万的div,即要让这个类从单例类变成一个普通的 可产生多个实例的类,那咱们必须改写creatDiv函数,把控制惟一对象的那一段去掉,这种修改会给咱们带来没必要要的烦恼。
3,用代理实现单例模式
如今咱们引入代理的方式来解决上面的问题,
首先咱们把负责管理单例的代码移除出来,使它成为一个普通的建立div的类,
var creatDiv = function () {
this.html = html;
this.init();
};
creatDiv.prototype.init = function () {
var div = document.createElement('div');
div.innerHTML = this.html;
document.body.appendChild(div);
};
接下来引入代理类,
var ProxySingletonCreatDiv = (function () {
var instance;
return function (html) {
if(!instance){
instance = new creatDiv( html );
}
return instance;
}
})();
var a = ProxySingletonCreatDiv('sev1');
var b = ProxySingletonCreatDiv('sev2');
alert( a === b );
经过引入代理类的方式,完成了一个单例模式的编写,跟以前不一样是把负责单例的逻辑和实现建立div的类分开了,这个是缓存代理的应用之一,后续还会有关于代理的一些应用。
4,JavaScript中的单例模式
前面的几种单例模式的实现,更多的是传统面向对象语言中的实现,单例对象从类中建立而来,在以类为中心的语言中,这是很天然的作法,好比在java中,若是须要某个对象,就必须先定义一个类,对象老是从类中建立而来。
可是javascript实际上是一门无类的语言,在js中建立对象的方法很是简单,既然咱们须要一个惟一的对象,为何还须要先建立一个类呢,传统的单例模式并不适用与JavaScript。
全局变量不是单例模式,可是在js中咱们常常会把全局变量当成单例模式来用,
例如 var a = {};
当用这种方式建立对象a的时候,对象a是独一无二的,若是a变量被声明在全局做用域下,则咱们能够在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所固然的,这样就知足了单例模式的两个条件。
做为一名js开发者,全局变量有多困扰就不用详细说了,连JavaScript的创造者本人也认可全局变量是设计失误,在es6中已经有对应的处理方式了。
全局变量的污染也是有解决方式的,
4.1,使用命名空间
只能减小,不能杜绝。例如:
var namespace = {
a:function(){
alert(1);
},
b:function(){
alert(2);
}
}
4.2,使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通讯。
var user = (function () {
var _name = 'sven',
_age = 29;
return {
getUserInfo : function () {
return _name + '-' + _age;
}
}
})();
5,惰性单例
前面咱们了解了单例模式的一些实现办法,如今来看下惰性单例。
惰性单例指的是在须要的时候才建立对象的实例,惰性单例是单例模式的重点,这种技术在实际开发中很是有用。就像是一开始的
instance
实例在咱们调用
getInstance
时才被建立,
Singleton.getInstance = (function () {
var instance = null;
return function (name) {
if(!instance){
instance = new Singleton(name);
}
return instance;
}
})();
不过这是基于类的单例模式,前面已经说过,这种是不适用于javascript的,结合一个登陆弹框的场景,来实现下惰性单例。
第一种解决方案就是,页面加载的时候就建立好,一开始隐藏,点击登陆的时候显示出来。
这种方式有个问题,若是用户在这个页面只是想浏览其余内容,不须要登陆,那么久浪费了一些dom节点。
若是改写下,在点击登陆的时候才开始建立弹窗,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<button id="btn">登陆</button>
</body>
<script>
var createLogin = function () {
var div = document.createElement('div');
div.innerHTML = '我是登陆弹窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
document.getElementById('btn').onclick = function () {
var login = createLogin();
login.style.display = 'block';
}
</script>
</html>
虽然达到了惰性的目的,可是失去了单例的效果,但咱们每次点击都会新建立一个弹窗,显然是很差,即便咱们能够在右上设置个将页面删除掉的按钮,可是频繁的建立和删除节点明显是不合理的。
咱们能够用变量判断页面是否已经建立弹窗,这也是基于类的单例模式的作法。creatLogin就能够改写成这样
var createLogin = (function () {
var div;
return function () {
if(!div){
div = document.createElement('div');
div.innerHTML = '我是登陆弹窗';
div.style.display = 'none';
document.body.appendChild(div);
}
return div;
}
})();
6,通用的惰性单例
上一节,咱们实现了一个可用的惰性单例,可是他仍是有一些问题,这段代码是违反了单一职责原则的,建立弹窗和管理单例的逻辑都在
creatLogin对象内部,若是咱们须要一个建立iframe的单例,那就要从新将这个函数在写一遍,能不能将管理单例的逻辑提出来呢,功能的实现是单独的函数。
管理单例的逻辑都放到getSingle函数里面,
var getSingle = function (fn) {
var result;
return function () {
return result || (result = fn.apply(this, arguments));
}
}
建立弹窗的函数就能够写成这样
var createLogin = function () {
var div = document.createElement('div');
div.innerHTML = '我是登陆弹窗';
div.style.display = 'none';
document.body.appendChild(div);
return div;
};
creatSingleLogin 就是个惰性单例的函数
var creatSingleLogin = getSingle(creatLogin);
若有其余实现的,建立iframe等其余的,均可以由getSingle这个来建立。
其实这种单例模式不仅是建立对象,好比咱们一般渲染完页面的中的一个列表以后,接下来要给这个列表绑定click事件,若是是经过ajax动态网列表里加数据,在使用事件代理的前提下,click事件实际上只须要在第一次渲染列表的时候绑定一次,可是咱们不想去判断当前是不是第一次渲染列表,若是借助jquery,咱们一般选择给节点绑定one事件:
var bindEvent = function () {
$('div').one('click', function () {
alert('click');
})
};
bindEvent();
bindEvent();
bindEvent();
虽然函数执行3次,可是绑定事件仍是只绑定了一次
用getSingle函数也能够达到同样的效果,
var bindEvent = getSingle(function () {
document.getElementById('btn').addEventListener( 'click', function () { //原书用的onclick,验证过这个执行几回也是执行一次,改为了事件绑定的模式
console.log('ssss')
});
return true;
});
bindEvent()
bindEvent()
这样的话,getSingle的入参函数必需要有个返回值,因此要 return true。
其实,像这样的场景最好的确定是事件委托,性能方面也会优化不少。
小结
单例模式是咱们学习的第一个模式,咱们先学习了传统的单例模式的实现,也了解由于语言的差别性,有更合适的方法在javascript中建立单例,这一章还提到了代理模式和单一职责原则。
在getSingle函数中,实际上也提到了闭包和高阶函数的概念,单例模式是一种简单但很是实用的模式,特别是惰性单例技术,在合适的时候才建立对象,而且只建立惟一的一个,更奇妙的是建立对象和管理单例的职责被分布在两个不一样的方法中,这两个方法组合起来才有单例模式的威力。
参考书
http://book.douban.com/subject/26382780/html