《Practical Common Lisp》的作者彼得高顺曾经说过,如果你需要一个模式,那一定有问题。他说的是,由于语言的固有缺陷,他不得不寻求和总结一个通用的解决方案。
无论是弱类型还是强类型,静态还是动态语言,命令式还是声明式语言,每种语言都有其固有的优点和缺点。牙买加运动员在短跑甚至拳击方面有一些优势,但在练习瑜伽方面缺乏一些。
术士和暗影牧师可以轻松成为优秀的助手,而一个拿着Macon全地图飞行的敌人会略显尴尬。在程序中,用静态语言实现decorator可能要花很多功夫,但是js可以随时在对象上抛出方法,这样decorator模式就成了js中的鸡肋。
关于Javascript设计模式的书很少,《Pro javaScript Design Patterns》是经典的一本,但是里面的例子比较啰嗦,所以结合我工作中写的代码总结一下我的理解。如果我的理解有任何偏差,请随时纠正我。
一.单一模式
singleton pattern的定义是产生类的唯一实例,但是js本身是一种无类语言。许多关于js设计模式的文章将{}作为一个单独的案例,这几乎没有意义。因为js可以以多种方式生成对象,所以让我们来看看另一个更有意义的单一情况。
有一个常见的需求是,当点击一个按钮时,页面上会弹出一个遮罩层。例如,当web.qq.com点击登录时。
这个生成灰色背景遮罩层的代码很容易写。
复制代码如下: var CREATE MASK=function(){ RETURN DOCUMENT,BODY。APPENDChild(文档。create element(div));}$(“按钮”)。单击(function(){ Var mask=create mask();mask . show();})
问题是这个遮罩层是全局唯一的,所以每次调用createMask都会创建一个新的div。虽然可以从隐藏掩膜层中去除,但这样做显然是不合理的。
看第二个方案,在页面的开头创建这个div,然后用变量引用它。
复制代码如下: varmask=document . body . append child(document . create element(" div "));$(“按钮”)。单击(function(){ mask . show();} )
这样,页面上只会创建一个遮罩层div,但接下来会出现另一个问题。也许我们永远都不需要这个遮罩层,然后我们会浪费一个div。dom节点上的任何操作都应该非常吝啬。
如果你能使用一个变量。判断div是否已经创建?
复制代码如下:var掩码;var create mask=function(){ if(mask)返回mask;else{mask=document,body . appendchild(document . create element(div));返回掩码;}}
看起来不错。这里已经完成了一个生成单列对象的函数。让我们仔细看看这段代码有什么问题。
首先,这个功能有一些副作用。函数体中外部变量掩码的引用已被更改。多人合作项目中,createMask是一个不安全的功能。另一方面,全局变量掩码不是必需的。让我们改进一下。
复制代码如下: VAR CREATE MASK=function(){ VAR MASK;return function(){ return mask | |(mask=document . body . appendchild(document . createelement(' div ')))} }()
变量掩码用简单的闭包包装,至少对于createMask函数,它是封闭的。
也许当你看到这个的时候,你会觉得单例模式太简单了。的确,有些设计模式非常简单。即使你从不关注设计模式的概念,你也会在平时的代码中不自觉地使用一些设计模式。就像很多年前,当我明白老人推车是什么的时候,我也以为尼玛原来是老人推车。
GOF有23种设计模式,它们已经存在了很长时间,并且在软件开发中被反复使用。如果程序员没有清楚地意识到自己使用了一些模式,下次可能会错过更合适的设计(这段话来自《松本行弘的程序世界》)。
回到正题,前面的单个例子还是有缺点的。它只能用于创建遮罩层。如果我需要编写一个函数来创建一个唯一的xhr对象呢?你能找到一个通用的单例包装器吗?
js中的函数是第一种类型,这意味着函数也可以作为参数传递。看看最后的代码。
复制代码如下: varsingleton=function(fn){ varresult;return函数(){return result || (result=fn。应用(这个,参数));} } var create mask=singleton(function(){ return document . body . appendchild(document . create element(' div '));})
变量用于第一次保存返回值。如果已经赋值,变量将在以后的调用中首先返回。实际创建屏蔽层的代码通过回调函数传递给单例包装器。这种方式其实叫做桥接模式。对于桥接模式,我们稍后再说。
然而,singleton函数并不完美,它仍然需要一个变量结果来注册div引用。不幸的是,js的功能特性不足以完全消除声明和语句。