记得早前就说过要和大家分享“javascript设计模式”,迟迟没写不是因为我懒,最近确实太忙,忙工作,忙旅游(啊哦?),好不容易这几天空闲了,接下来是兑现之前空口白话的时间了。 在讨论设计模式之前,请确认您已经有一定的脚本编程基础,如果不甚了解,建议可以先查阅本人很久之前写的这篇《浅谈javascript面向对象编程》请看下一篇文章。 讲到设计模式,不得不先重点着墨于“接口设计”,因为接口设计在设计模式中的意义太大了,大于模式本身。直观起见,先介绍一下接口定义的形式: 复制代码 代码如下: var interface = new Interface("interface",[["getName",1],["getAge",1]]);
上例是一个最简单的单体模式,把本人的资料都整合到ioldfish这个对象字面量中,形成一个模块,同时起到了一个命名空间的作用。 复制代码 代码如下: var ioldfish =(function(){ var name = "老鱼"; var age = 27; return{ getName:function(){ alert(name); }, getAge:function(){ alert(age); } } })();
对第一个单体做简单的修改,通过闭包让name,age成为静态私有变量,确保实例化的时候在内存中始终只有一份,这样更符合单体模式的定义。 下面重点介绍一下惰性单体,废话少说,先看看我们该如何来实现惰性单体: 复制代码 代码如下: var ioldfish = (function(){ var uniqueInstance; var name = "老鱼"; var age = 27; function constructor(){ return{ getName:function(){ alert(name); }, getAge:function(){ alert(age); } } } return{ isInstance:function(){ if(uniqueInstance == null){ uniqueInstance = constructor(); } return uniqueInstance; } } })(); ioldfish.isInstance().getName();
上面的结构公私分明一目了然,私有变量uniqueInstance(标识类是否已经实例化)和私有方法constructor,返回一个公有方法isInstance(通过该方法可以调用私有方法constructor中定义的方法),形如:ioldfish.isInstance().getName();先通过isInstance()方法判断其是否被实例化,然后通过getName()方法获取到闭包内的私有变量name。该模式的应用场景还是很多的,是不是遇见过页面中需要加载很大的一个日历控件,但并非所有用户都用的到呢?是不是… Javascript设计模式之工厂模式Factory 工厂模式Factory:先创建一个抽象类,然后基于这个抽象类派生出子类,并在子类中创建工厂方法,从而把实例化推迟到对应的子类中进行,说实话,工厂模式在javascript中的应用有些牵强,毕竟javascript不像java存在硬编码带来的困搅,要学习的只是模式的思想,切忌因为模式而模式。 不妨举个偏激点的例子,为tab切换、下拉列表等组件添加定位,渐隐,延迟等效果,我们可以先为这些组件定义一个接口: var Iwidget = new Interface("iwidget",[["addEffect"]]); 定义该接口,以便之后派生的子类继承,接口中定义了一个addEffect方法,接口方法实现后,调用的同学大可不必关注各子类中对于addEffect方法的代码实现。 复制代码 代码如下: var Widget = function(){}; Widget.prototype={ fire:function(model){ var widget = this.createWidget(model); //有同学问为什么子类都必须定义接口方法,因为下面要调用嘛 widget.addEffect(); return widget; }, show:function(){ //show代码具体实现 }, hide:function(){ //hide代码具体实现 }, createWidget:function(model){ alert("抽象类,不可以实例化") } };
上例先定义一个抽象类Widget,做为派生子类的父类,由于考虑到这两类组件都涉及到隐藏和显示一个容器,所以在父类中预先定义好show和hide方法以便子类继承。 复制代码 代码如下: var xTab = function(){}; extend(xTab,Widget); xTab.prototype.createWidget = function(model){ var widget; switch(model){ case "position": widget = new xTabPosition(); break; case "anim": widget = new xTabAnim(); break; case "delay": default: widget = new xTabDelay(); } }; var dropDown = function(){}; extend(dropDown,Widget); dropDown.prototype.createWidget = function(model){ var widget; switch(model){ case "position": widget = new dropDownPosition(); break; case "anim": widget = new dropDownAnim(); break; case "delay": default: widget = new dropDownDelay(); } };
子类xTab和dropDown继承了父类,并且重写了createWidget方法,不同的子类根据定位,渐隐,延迟效果分别创建不同的实例,只要创建这些实例的类都实现接口中约定的addEffect方法,至于方法代码如何实现,千篇一律,爱咋整咋整。 复制代码 代码如下: var xTabPosition = function(){}; xTabPosition.prototype ={ addEffect:function(){ //具体实现代码 } }; var dropDownPosition = function(){}; dropDownPosition.prototype ={ addEffect:function(){ //具体实现代码 } }; var dropDownInstance = new dropDown(); dropDownInstance.fire("position");
最后设置一个单体对象,将操作银行的相关信息形成一个模块,方便调用: 复制代码 代码如下: var BankAction ={ bankList:[], addBank:function(card){ this.bankList.push(card); }, innerBank:function(conId){ for(i=0;len=this.bankList.length,i<len;i++){ var cardObj =this.bankList[i].getElement(); } document.getElementById(conId).appendChild(cardObj); } };
到了实现环节了,实例化生成一个包含所有卡的最外层容器,然后根据卡类,分别生成一个放置银行卡和卡通卡的容器,最后生成各银行卡的实例,并按层级关系形成DOM结构: 复制代码 代码如下: var bankDivT = new cardMain("PayCard");//创建最外层容器 var ebankCard = new cardMain("ebankCard");//创建网银类银行卡容器 var ktCard = new cardMain("ktCard");//创建卡通类银行卡容器 var ccbBank = new bankLogo("Ebank-CMB");//创建招行银行卡 var abcBank = new bankLogo("Ebank-ABC");//创建农行银行卡 var abcKtBank = new bankLogo("Kt-ABC");//创建卡通农行卡 ebankCard.add(ccbBank); ebankCard.add(abcBank); ktCard.add(abcKtBank); bankDivT.add(ebankCard); bankDivT.add(ktCard); BankAction.addBank(bankDivT); BankAction.innerBank("bankList");