《parctical common lisp》的做者曾说,若是你须要一种模式,那必定是哪里出了问题。他所说的问题是指由于语言的天生缺陷,不得不去寻求和总结一种通用的解决方案。javascript
不论是弱类型或强类型,静态或动态语言,命令式或说明式语言、每种语言都有天生的优缺点。一个牙买加运动员, 在短跑甚至拳击方面有一些优点,在练瑜伽上就欠缺一些。java
术士和暗影牧师很容易成为一个出色的辅助,而一个背着梅肯满地图飞的敌法就会略显尴尬。 换到程序中, 静态语言里可能须要花不少功夫来实现装饰者,而js因为能随时往对象上面扔方法,以致于装饰者模式在js里成了鸡肋。程序员
讲javascript设计模式的书还比较少. Pro javaScript Design Patterns.是比较经典的一本,可是它里面的例子举得比较啰嗦,因此结合我在工做中写过的代码,把个人理解总结一下。若是个人理解出现了误差,请不吝指正。web
一 单例模式设计模式
单例模式的定义是产生一个类的惟一实例,但js自己是一种“无类”语言。不少讲js设计模式的文章把{}当成一个单例来使用也勉强说得通。由于js生成对象的方式有不少种,咱们来看下另外一种更有意义的单例。安全
有这样一个常见的需求,点击某个按钮的时候须要在页面弹出一个遮罩层。好比web.qq.com点击登陆的时候.闭包
这个生成灰色背景遮罩层的代码是很好写的.app
var createMask = function(){
return document.body.appendChild( document.createElement('div') );
}dom
$( 'button' ).click( function(){
Var mask = createMask();
mask.show();
})函数
问题是, 这个遮罩层是全局惟一的, 那么每次调用createMask都会建立一个新的div, 虽然能够在隐藏遮罩层的把它remove掉. 但显然这样作不合理.
再看下第二种方案, 在页面的一开始就建立好这个div. 而后用一个变量引用它.
var mask = document.body.appendChild( document.createElement( 'div' ) );
$( 'button' ).click( function(){
mask.show();
} )
这样确实在页面只会建立一个遮罩层div, 可是另一个问题随之而来, 也许咱们永远都不须要这个遮罩层, 那又浪费掉一个div, 对dom节点的任何操做都应该很是吝啬.
若是能够借助一个变量. 来判断是否已经建立过div呢?
var mask;
var createMask = function(){
if ( mask ) return mask;
else{
mask = document,body.appendChild( document.createElement('div') );
return mask;
}
}
看起来不错, 到这里的确完成了一个产生单列对象的函数. 咱们再仔细看这段代码有什么不妥.
首先这个函数是存在必定反作用的, 函数体内改变了外界变量mask的引用, 在多人协做的项目中, createMask是个不安全的函数. 另外一方面, mask这个全局变量并非非需不可. 再来改进一下.
var createMask = function(){
var mask;
return function(){
return mask || ( mask = document.body.appendChild( document.createElement('div') ) )
}
}()
用了个简单的闭包把变量mask包起来, 至少对于createMask函数来说, 它是封闭的.
可能看到这里, 会以为单例模式也太简单了. 的确一些设计模式都是很是简单的, 即便从没关注过设计模式的概念, 在平时的代码中也不知不觉用到了一些设计模式. 就像多年前我明白老汉推车是什么回事的时候也想过尼玛原来这就是老汉推车.
GOF里的23种设计模式, 也是在软件开发中早就存在并反复使用的模式. 若是程序员没有明确意识到他使用过某些模式, 那么下次他也许会错过更合适的设计 (这段话来自《松本行弘的程序世界》).
再回来正题, 前面那个单例仍是有缺点. 它只能用于建立遮罩层. 假如我又须要写一个函数, 用来建立一个惟一的xhr对象呢? 能不能找到一个通用的singleton包装器.
js中函数是第一型, 意味着函数也能够当参数传递. 看看最终的代码.
var singleton = function( fn ){
var result;
return function(){
return result || ( result = fn .apply( this, arguments ) );
}
}
var createMask = singleton(
function(){
return document.body.appendChild( document.createElement('div') );
}
)
用一个变量来保存第一次的返回值, 若是它已经被赋值过, 那么在之后的调用中优先返回该变量. 而真正建立遮罩层的代码是经过回调函数的方式传人到singleton包装器中的. 这种方式其实叫桥接模式. 关于桥接模式, 放在后面一点点来讲.
然而singleton函数也不是完美的, 它始终仍是须要一个变量result来寄存div的引用. 遗憾的是js的函数式特性还不足以彻底的消除声明和语句.