前端面试之JavaScript设计模式

2019-06-08  本文已影响0人  Web前端学习营

面向对象编程就是将你的需求抽象成一个对象,然后对这个对象进行分析,为其添加对应的特征(属性)与行为(方法),我们将这个对象称之为  。 面向对象一个很重要的特点就是封装,虽然 javascript 这种解释性的弱类型语言没有像一些经典的强类型语言(例如 C++,JAVA等)有专门的方式用来实现类的封装,但我们可以利用 javascript 语言灵活的特点,去模拟实现这些功能。而在许多大型 web 项目中国, JavaScript 代码的数量已经非常多了,我们有必要将一些优秀的设计模式借鉴到 JavaScript 中。

装饰者模式

给对象动态地添加职责的方式称为装饰者模式。传统的面向对象语言中给对象添加功能常常使用继承的方式,但是继承的方式不灵活,而与之相比,装饰者模式更加灵活,“即用即付”。

AOP 装饰函数:

Function.prototype.before=function(fn){constself= thisreturnfunction(){    fn.apply(new(self), arguments)// https://github.com/MuYunyun/blog/pull/30#event-1817065820returnself.apply(new(self), arguments)  }}Function.prototype.after=function(fn){constself= thisreturnfunction(){self.apply(new(self), arguments)returnfn.apply(new(self), arguments)  }}复制代码

场景:插件式的表单验证

用户名:密码:varusername =document.getElementById('username'),password =document.getElementById('password'),submitBtn =document.getElementById('submitBtn');varformSubmit =function(){if( username.value ===''){returnalert ('用户名不能为空');}if( password.value ===''){returnalert ('密码不能为空');}varparam = {username: username.value,password: password.value}ajax('http:// xxx.com/login', param );// ajax 具体实现略}submitBtn.onclick =function(){formSubmit();}varvalidata =function(){if( username.value ===''){alert ('用户名不能为空');returnfalse;}if( password.value ===''){alert ('密码不能为空');returnfalse;}}复制代码

上面formSubmit函数除了提交ajax请求之外,还要验证用户输入的合法,这样会造成函数臃肿,职责混乱,我们可以分离检验输入和ajax请求的代码,我们把校验输入的逻辑放在validata函数中,并且约定当validata函数返回false的时候,表示校验未通过

Function.prototype.before =function( beforefn ){var__self =this;returnfunction(){if( beforefn.apply(this,arguments) ===false){// beforefn 返回false 的情况直接return,不再执行后面的原函数return;}return__self.apply(this,arguments);}}varvalidata =function(){if( username.value ===''){alert ('用户名不能为空');returnfalse;}if( password.value ===''){alert ('密码不能为空');returnfalse;}}varformSubmit =function(){varparam = {username: username.value,password: password.value}ajax('http:// xxx.com/login', param );}formSubmit = formSubmit.before( validata );submitBtn.onclick =function(){formSubmit();}复制代码

发布-订阅模式

发布-订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。在JavaScript中,我们一般用事件模型来替代传统的发布-订阅模式。

场景:售房信息通知

varsalesOffices = {};// 定义售楼处salesOffices.clientList = [];// 缓存列表,存放订阅者的回调函数salesOffices.listen =function( key, fn ){if( !this.clientList[ key ] ){// 如果还没有订阅过此类消息,给该类消息创建一个缓存列表this.clientList[ key ] = [];}this.clientList[ key ].push( fn );// 订阅的消息添加进消息缓存列表};salesOffices.trigger =function(){// 发布消息varkey =Array.prototype.shift.call(arguments),// 取出消息类型fns =this.clientList[ key ];// 取出该消息对应的回调函数集合if( !fns || fns.length ===0){// 如果没有订阅该消息,则返回returnfalse;}for(vari =0, fn; fn = fns[ i++ ]; ){fn.apply(this,arguments);// (2) // arguments 是发布消息时附送的参数}};salesOffices.listen('squareMeter88',function( price ){// 小明订阅88 平方米房子的消息console.log('价格= '+ price );// 输出: 2000000});salesOffices.listen('squareMeter110',function( price ){// 小红订阅110 平方米房子的消息console.log('价格= '+ price );// 输出: 3000000});salesOffices.trigger('squareMeter88',2000000);// 发布88 平方米房子的价格salesOffices.trigger('squareMeter110',3000000);// 发布110 平方米房子的价格复制代码

状态模式

状态模式的关键在于区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变

状态模式定义了状态与行为之间的关系,并将它们封装在一个类中。

context中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响

场景:电灯程序

电灯开关按钮被按下时,第一次按下打开弱光,第二次按下打开强光,第三次是关闭电灯。我们谈到封装,一般都会优先封装对象的行为,而不是对象的状态。但在状态模式中刚好相反,状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部,所以button被按下的时候,只需要在上下文中,把这个请求委托给当前的状态对象即可,该状态对象会负责渲染它自身的行为。

varOffLightState =function( light ){this.light = light;};OffLightState.prototype.buttonWasPressed =function(){console.log('弱光');// offLightState 对应的行为this.light.setState(this.light.weakLightState );// 切换状态到weakLightState};// WeakLightState:varWeakLightState =function( light ){this.light = light;};WeakLightState.prototype.buttonWasPressed =function(){console.log('强光');// weakLightState 对应的行为this.light.setState(this.light.strongLightState );// 切换状态到strongLightState};// StrongLightState:varStrongLightState =function( light ){this.light = light;};StrongLightState.prototype.buttonWasPressed =function(){console.log('关灯');// strongLightState 对应的行为this.light.setState(this.light.offLightState );// 切换状态到offLightState};//这个light称为上下文(context)varLight =function(){//在light的构造函数中,我们要创建每一个状态类的实例对象,context持有这些状态对象的引用,以便将请求委托给状态对象。用户的请求,即点击button的动作也是实现在Context中的this.offLightState =newOffLightState(this);this.weakLightState =newWeakLightState(this);this.strongLightState =newStrongLightState(this);this.button =null;};Light.prototype.init =function(){varbutton =document.createElement('button'),self =this;this.button =document.body.appendChild( button );this.button.innerHTML ='开关';this.currState =this.offLightState;// 设置当前状态this.button.onclick =function(){self.currState.buttonWasPressed();}};Light.prototype.setState =function( newState ){this.currState = newState;};varlight =newLight();light.init();复制代码

JavaScript状态机:

vardelegate =function( client, delegation ){return{buttonWasPressed:function(){// 将客户的操作委托给delegation 对象returndelegation.buttonWasPressed.apply( client,arguments);}}};varFSM = {off: {buttonWasPressed:function(){console.log('关灯');this.button.innerHTML ='下一次按我是开灯';this.currState =this.onState;}},on: {buttonWasPressed:function(){console.log('开灯');this.button.innerHTML ='下一次按我是关灯';this.currState =this.offState;}}};varLight =function(){this.offState = delegate(this, FSM.off );this.onState = delegate(this, FSM.on );this.currState =this.offState;// 设置初始状态为关闭状态this.button =null;};Light.prototype.init =function(){varbutton =document.createElement('button'),self =this;button.innerHTML ='已关灯';this.button =document.body.appendChild( button );this.button.onclick =function(){self.currState.buttonWasPressed();}};varlight =newLight();light.init();复制代码

代理模式

代理有很多种类,比如 保护代理 会过滤请求,代理B可以拒绝将A发起的请求给传递给C;虚拟代理会将一些操作交给代理执行,代理B可以在A满足某些条件下再执行操作,即虚拟代理会把一些开销大的对象延迟到真正需要它的时候再去创建。保护代理用于控制不同权限的对象对目标对象的访问,但在JavaScript中并不太容易实现保护代理,因为我们无法判断谁访问了某个对象。

代理和 本体 的接口必须保持一致性,对于用户,他们并不清楚代理和本体的区别,在任何使用本体的地方都可以替换成使用代理。

场景:图片预加载

//只有本体varmyImage=(function(){varimgNode=document.createElement('img');document.body.appendChild(imgNode);return{setSrc:function(src){            imgNode.src=src;        }    }})myImg.setSrc('./logo.lpg');//有本体和代理的情况;引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面将出现一张占位的菊花图来提示用户图片正在被加载varmyImage=(function(){varimgNode=document.createElement('img');document.body.appendChild(imgNode);return{setSrc:function(src){            imgNode.src=src;        }    }})()varproxyImage=(function(){varimg=newImage;    img.onload=function(){        myImage.setSrc(this.src);    }return{setSrc:function(src){            myImage.setSrc('./loading.gif');            img.src=src;        }    }})()proxyImage.setSrc('./logo.jpg')复制代码

策略模式

策略模式的目的就是将算法的使用与算法的实现分离开,一个策略模式的程序至少由两部分组成。第一个部分是策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。

传统面向对象语言实现

/*strategy类*/varperformanceS=function(){};performanceS.prototype.caculate=function(salary){returnsalary*4;}varperformanceA=function(){};performanceA.prototype.caculate=function(salary){returnsalary*3;}varperformanceB=function(){};performanceB.proptype.caculate=function(salary){returnsalary*2;}/*Context类*/varBonus=function(){this.salary=null;this.strategy=null;}Bonus.prototype.setSalary=function(salary){this.salary=salary;}Bonus.prototype.setStrategy=function(strategy){this.strategy=strategy;}Bonus.prototype.getBonus=function(){returnthis.strategy.caculate(this.salary);}varbonus=newBonus();bonus.setSalary(1000);bonus.setStrategy(newperformanceS());console.log(bonus.getBonus());复制代码

Javascript中实现

在JavaScript中,Stratege我们可以把它定义为函数对象,Context负责接收用户的请求并委托给strategy对象

varstrategies={"S":function(salary){returnsalary*4;    },"A":function(salary){returnsalary*3;    },"B":function(salary){returnsalary*2;    }};varcalculateBonus=function(level,salary){returnstrategies[level](salary);};console.log(calculateBonus('S',2000));复制代码

场景:表单检验

/*Validator类的实现*/varValidator =function(){    this.cache = [];}Validator.prototype.add =function(dom,rule,errorMsg){varary = rule.split(':');    this.cache.push(function(){varstrategy =ary.shift();        ary.unshift(dom.value);        ary.push(errorMsg);returnstrategies[strategy].apply(dom,ary);    });};Validator.prototype.start =function(){for(vari=0,validatorFunc;validatorFunc = this.cache[i++]){varmsg=validatorFunc();if(msg){returnmsg;        }    }}/*定义strategy*/varstrategies = {    isNonEmpty:function(value,errorMsg){if(value=''){returnerrorMsg;        }    },    minLength:function(value,length,errorMsg){if(value.length

    validator.add(registerForm.password,'inLength:6',密码长度不能少于6位'')

    validator.add(registerForm.phoneNumber,'isMobile','手机号码格式不正确’);varerrorMsg = validator.start();returnerrorMsg;}varregisterForm = document.getElementById("registerForm");registerForm.onsubmit =function(){    varerrorMsg = validataFunc();if(errorMsg){        alert(errorMsg);returnfalse;    }}复制代码

职责链模式

职责链模式: 类似多米诺骨牌, 通过请求第一个条件, 会持续执行后续的条件, 直到返回结果为止。

重要性: 4 星, 在项目中能对 if-else 语句进行优化

场景 :手机售卖

场景: 某电商针对已付过定金的用户有优惠政策, 在正式购买后, 已经支付过 500 元定金的用户会收到 100 元的优惠券, 200 元定金的用户可以收到 50 元优惠券, 没有支付过定金的用户只能正常购买。

// orderType: 表示订单类型, 1: 500 元定金用户;2: 200 元定金用户;3: 普通购买用户// pay: 表示用户是否已经支付定金, true: 已支付;false: 未支付// stock: 表示当前用于普通购买的手机库存数量, 已支付过定金的用户不受此限制constorder =function( orderType, pay, stock ){if( orderType ===1) {if( pay ===true) {console.log('500 元定金预购, 得到 100 元优惠券')    }else{if(stock >0) {console.log('普通购买, 无优惠券')      }else{console.log('库存不够, 无法购买')      }    }  }elseif( orderType ===2) {if( pay ===true) {console.log('200 元定金预购, 得到 50 元优惠券')    }else{if(stock >0) {console.log('普通购买, 无优惠券')      }else{console.log('库存不够, 无法购买')      }    }  }elseif( orderType ===3) {if(stock >0) {console.log('普通购买, 无优惠券')    }else{console.log('库存不够, 无法购买')    }  }}order(3,true,500)// 普通购买, 无优惠券复制代码

下面用职责链模式改造代码:

constorder500 =function(orderType, pay, stock){if( orderType ===1&& pay ===true) {console.log('500 元定金预购, 得到 100 元优惠券')  }else{    order200(orderType, pay, stock)  }}constorder200 =function(orderType, pay, stock){if( orderType ===2&& pay ===true) {console.log('200 元定金预购, 得到 50 元优惠券')  }else{    orderCommon(orderType, pay, stock)  }}constorderCommon =function(orderType, pay, stock){if(orderType ===3&& stock >0) {console.log('普通购买, 无优惠券')  }else{console.log('库存不够, 无法购买')  }}order500(3,true,500)// 普通购买, 无优惠券复制代码

改造后可以发现代码相对清晰了, 但是链路代码和业务代码依然耦合在一起, 进一步优化:

// 业务代码constorder500 =function(orderType, pay, stock){if( orderType ===1&& pay ===true) {console.log('500 元定金预购, 得到 100 元优惠券')  }else{return'nextSuccess'}}constorder200 =function(orderType, pay, stock){if( orderType ===2&& pay ===true) {console.log('200 元定金预购, 得到 50 元优惠券')  }else{return'nextSuccess'}}constorderCommon =function(orderType, pay, stock){if(orderType ===3&& stock >0) {console.log('普通购买, 无优惠券')  }else{console.log('库存不够, 无法购买')  }}// 链路代码constchain =function(fn){this.fn = fnthis.sucessor =null}chain.prototype.setNext =function(sucessor){this.sucessor = sucessor}chain.prototype.init =function(){constresult =this.fn.apply(this,arguments)if(result ==='nextSuccess') {this.sucessor.init.apply(this.sucessor,arguments)  }}constorder500New =newchain(order500)constorder200New =newchain(order200)constorderCommonNew =newchain(orderCommon)order500New.setNext(order200New)order200New.setNext(orderCommonNew)order500New.init(3,true,500)// 普通购买, 无优惠券复制代码

重构后, 链路代码和业务代码彻底地分离。假如未来需要新增 order300, 那只需新增与其相关的函数而不必改动原有业务代码。

另外结合 AOP 还能简化上述链路代码:

// 业务代码constorder500 =function(orderType, pay, stock){if( orderType ===1&& pay ===true) {console.log('500 元定金预购, 得到 100 元优惠券')  }else{return'nextSuccess'}}constorder200 =function(orderType, pay, stock){if( orderType ===2&& pay ===true) {console.log('200 元定金预购, 得到 50 元优惠券')  }else{return'nextSuccess'}}constorderCommon =function(orderType, pay, stock){if(orderType ===3&& stock >0) {console.log('普通购买, 无优惠券')  }else{console.log('库存不够, 无法购买')  }}// 链路代码Function.prototype.after =function(fn){constself =thisreturnfunction(){constresult = self.apply(self,arguments)if(result ==='nextSuccess') {returnfn.apply(self,arguments)// 这里 return 别忘记了~}  }}constorder = order500.after(order200).after(orderCommon)order(3,true,500)// 普通购买, 无优惠券复制代码

职责链模式比较重要, 项目中能用到它的地方会有很多, 用上它能解耦 1 个请求对象和 n 个目标对象的关系。

单例模式

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

应用:线程池、全局缓存,浏览器中的window对象等

传统面向对象语言中实现

varSingleTon =function(name){this.name=name;this.instance=null;}SingleTon.proptype.getName=function(){    alert(this.name);}SingleTon.getInstance=function(name){if(!this.instance){this.instance=newSingleTon(name);    }returnthis.instance;}vara=SingleTon.getInstance('sven1');varb=SingleTon.getInstance('sven2');复制代码

或者将this.intance改为局部变量的形式

varSingleTon =function(name){this.name=name;}SingleTon.proptype.getName=function(){    alert(this.name);}SingleTon.getInstance=function(name){varinstance=null;returnfunction(name){if(!instance) {this.instance =newSingleTon(name);        }returnthis.instance;    }}vara=SingleTon.getInstance('sven1');varb=SingleTon.getInstance('sven2');复制代码

更加透明的单例类,用户创建对象时,可以像使用其他任何普通类一样

//这段代码既创建了对象,又返回了单例,跟其他创建对象方式无异varCreateDiv=(function(){varinstance;varCreateDiv=function(html){if(instance){returninstance;        }this.htm=html;this.init();returninstance=this;    };    CreateDiv.proptype.init=function(){vardiv=document.createElement('div');        div.innerHTML=this.html;document.body.appendChild(div);    };returnCreateDiv;})()vara=newCreateDiv('sven1');varb=newCreateDiv('sven2');复制代码

我们还需要将创建对象,执行初始化init方法与管理单例的逻辑分开,这里引入代理类来优化代码

varCreateDiv=function(html){this.html = html;this.init();}CreateDiv.proptype.init=function(){vardiv=document.createElement('div');    div.innerHTML=this.html;document.body.appendChild(div);};varProxySingleTonCreateDiv=(function(){varinstance;returnfunction(html){if(!instance) {            instance =newCreateDiv(html);        }returninstance;    };})()vara=newCreateDiv('sven1');varb=newCreateDiv('sven2');复制代码

Javascript中实现

JavaScript是无类的语言,在JavaScript中,我们通过全局变量来实现单例模式,但是全局变量经常带来命名污染的问题,解决方案如下:

1.使用命名空间

varnamespace1={    a:function(){        alert(1);    },    b:function(){        laert(2);    }}复制代码

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

varuser=(function(){var_name='sven',_age=29;return{        getUserInfo:function(){return_name+'-'+'_age';        }    }})复制代码

中介者模式

中介者模式是指对象和对象之间借助第三方中介者进行通信.中介模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系。各个对象只需关注自身的实现,对象之间的交互关系交给了中介者来实现和维护。

一场测试结束后, 公布结果: 告知解答出题目的人挑战成功, 否则挑战失败。

constplayer =function(name){this.name = name  playerMiddle.add(name)}player.prototype.win =function(){  playerMiddle.win(this.name)}player.prototype.lose =function(){  playerMiddle.lose(this.name)}//在这段代码中 A、B、C 之间没有直接发生关系, 而是通过另外的 playerMiddle 对象建立链接, 姑且将之当成是中介者模式了constplayerMiddle = (function(){constplayers = []constwinArr = []constloseArr = []return{add:function(name){      players.push(name)    },win:function(name){      winArr.push(name)if(winArr.length + loseArr.length === players.length) {this.show()      }    },lose:function(name){      loseArr.push(name)if(winArr.length + loseArr.length === players.length) {this.show()      }    },show:function(){for(letwinnerofwinArr) {console.log(winner +'挑战成功;')      }for(letloserofloseArr) {console.log(loser +'挑战失败;')      }    },  }}())consta =newplayer('A 选手')constb =newplayer('B 选手')constc =newplayer('C 选手')a.win()b.win()c.lose()复制代码

适配器模式

适配器模式用来解决两个软件实体的接口不兼容的问题

vargoogleMap = {show:function(){console.log('开始渲染谷歌地图');}};varbaiduMap = {display:function(){console.log('开始渲染百度地图');}};varrenderMap =function( map ){if( map.showinstanceofFunction){map.show();}};//解决百度地图中展示地图的方法名与谷歌地图中展示地图的方法名不一样的问题varbaiduMapAdapter = {show:function(){returnbaiduMap.display();}};renderMap( googleMap );// 输出:开始渲染谷歌地图renderMap( baiduMapAdapter );// 输出:开始渲染百度地图复制代码

迭代器模式

迭代器是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素。

迭代器的实现:

var each=function(arry,callback){for(vari=0,l=ary.length;i

内部迭代器

上面的each函数即使内部迭代器,由于内部迭代器的迭代规则已经被提前规定,上面的each函数就无法同时迭代2个数组了,不如现在如果我们要实现判断2个数组里面的元素的值是否完全相等,如果不能修改each函数本身的话就只能修改each的回调函数了

varcompare=function(ary1,ary2){if(ary1.length!=ary2.length){thrownewError("ary1和ary2不相等")    }    each(ary1,funtion(i,n){if(n!==ary2[i]){            thorwnewError('ary1和ary2不相等')        }    })    alert('ary1和ary2相等')}compare([1,2,3],[1,2,4]);复制代码

外部迭代器

外部迭代器必须显式的请求迭代下一个元素,外部迭代器增加了一些调用的复杂度,但相对也增强了迭代器的灵活性,我们可以手工控制迭代的过程或顺序

varInterator=function(obj){varcurrent=0;varnext=funtion(){        current+=1;    }varisDone=function(){returncurrent>=obj.length;    }vargetCurrItem=function(){returnobj[current];    }return{        next:next,        isDone:isDone,    getCurrItem:getCurrItem,        length:obj.length    }}复制代码

组合模式

组合模式在对象间形成 树形结构 ;

组合模式中基本对象和组合对象被 一致对待 ;

无须关心对象有多少层, 调用时只需在根部进行调用;

扫描文件夹时, 文件夹下面可以为另一个文件夹也可以为文件, 我们希望统一对待这些文件夹和文件, 这种情形适合使用组合模式。

constFolder =function(folder){this.folder = folderthis.lists = []}Folder.prototype.add =function(resource){this.lists.push(resource)}Folder.prototype.scan =function(){console.log('开始扫描文件夹: ',this.folder)for(leti =0, folder; folder =this.lists[i++];) {    folder.scan()  }}constFile =function(file){this.file = file}File.prototype.add =function(){throwError('文件下不能添加其它文件夹或文件')}File.prototype.scan =function(){console.log('开始扫描文件: ',this.file)}constfolder =newFolder('根文件夹')constfolder1 =newFolder('JS')constfolder2 =newFolder('life')constfile1 =newFile('深入React技术栈.pdf')constfile2 =newFile('JavaScript权威指南.pdf')constfile3 =newFile('小王子.pdf')folder1.add(file1)folder1.add(file2)folder2.add(file3)folder.add(folder1)folder.add(folder2)folder.scan()// 开始扫描文件夹:  根文件夹// 开始扫描文件夹:  JS// 开始扫描文件:  深入React技术栈.pdf// 开始扫描文件:  JavaScript权威指南.pdf// 开始扫描文件夹:  life// 开始扫描文件:  小王子.pdf复制代码

享元模式

享元模式是一种优化程序性能的模式, 本质为减少对象创建的个数。

以下情况可以使用享元模式:

有大量相似的对象, 占用了大量内存

对象中大部分状态可以抽离为外部状态

某商家有 50 种男款内衣和 50 种款女款内衣, 要展示它们

方案一: 造 50 个塑料男模和 50 个塑料女模, 让他们穿上展示, 代码如下:

constModel =function(gender, underwear){this.gender = genderthis.underwear = underwear}Model.prototype.takephoto =function(){console.log(`${this.gender}穿着${this.underwear}`)}for(leti =1; i <51; i++) {constmaleModel =newModel('male',`第${i}款衣服`)  maleModel.takephoto()}for(leti =1; i <51; i++) {constfemale =newModel('female',`第${i}款衣服`)  female.takephoto()}复制代码

方案二: 造 1 个塑料男模特 1 个塑料女模特, 分别试穿 50 款内衣

constModel =function(gender){this.gender = gender}Model.prototype.takephoto =function(){console.log(`${this.sex}穿着${this.underwear}`)}constmaleModel =newModel('male')constfemaleModel =newModel('female')for(leti =1; i <51; i++) {  maleModel.underwear =`第${i}款衣服`maleModel.takephoto()}for(leti =1; i <51; i++) {  femaleModel.underwear =`第${i}款衣服`femaleModel.takephoto()}复制代码

对比发现: 方案一创建了 100 个对象, 方案二只创建了 2 个对象, 在该 demo 中, gender(性别) 是内部对象, underwear(穿着) 是外部对象。

当然在方案二的 demo 中, 还可以进一步改善:

一开始就通过构造函数显示地创建实例, 可用工场模式将其升级成可控生成

在实例上手动添加 underwear 不是很优雅, 可以在外部单独在写个 manager 函数

constModel =function(gender){this.gender = gender}Model.prototype.takephoto =function(){console.log(`${this.gender}穿着${this.underwear}`)}constmodelFactory = (function(){// 优化第一点constmodelGender = {}return{createModel:function(gender){if(modelGender[gender]) {returnmodelGender[gender]      }returnmodelGender[gender] =newModel(gender)    }  }}())constmodelManager = (function(){constmodelObj = {}return{add:function(gender, i){      modelObj[i] = {underwear:`第${i}款衣服`}returnmodelFactory.createModel(gender)    },copy:function(model, i){// 优化第二点model.underwear = modelObj[i].underwear    }  }}())for(leti =1; i <51; i++) {constmaleModel = modelManager.add('male', i)  modelManager.copy(maleModel, i)  maleModel.takephoto()}for(leti =1; i <51; i++) {constfemaleModel = modelManager.add('female', i)  modelManager.copy(femaleModel, i)  femaleModel.takephoto()}复制代码

模板方法模式

模板方法是一种基于继承的设计模式,模板方法模式有两个部分组成,第一个是抽象父类,第二个是具体的实现子类。但在JavaScript中,我们很多时候都不需要画瓢一样去实现一个模板方法模式,高阶函数是更好的选择。

varCoffee =function(){};Coffee.prototype.boilWater =function(){console.log('把水煮沸');};Coffee.prototype.brewCoffeeGriends =function(){console.log('用沸水冲泡咖啡');};Coffee.prototype.pourInCup =function(){console.log('把咖啡倒进杯子');};Coffee.prototype.addSugarAndMilk =function(){console.log('加糖和牛奶');};Coffee.prototype.init =function(){this.boilWater();this.brewCoffeeGriends();this.pourInCup();this.addSugarAndMilk();};varcoffee =newCoffee();coffee.init();varTea =function(){};Tea.prototype.boilWater =function(){console.log('把水煮沸');};Tea.prototype.steepTeaBag =function(){console.log('用沸水浸泡茶叶');};Tea.prototype.pourInCup =function(){console.log('把茶水倒进杯子');};Tea.prototype.addLemon =function(){console.log('加柠檬');};Tea.prototype.init =function(){this.boilWater();this.steepTeaBag();this.pourInCup();this.addLemon();};vartea =newTea();tea.init();复制代码

泡茶和泡咖啡的过程中有几个是一样的,我们可以定义一个Beverage类,定义泡茶和泡咖啡中的共同动作,而泡茶和泡咖啡中独有的动作可以直接重写Beverage类中的方法即可

varBeverage =function( param ){varboilWater =function(){console.log('把水煮沸');};varbrew = param.brew ||function(){thrownewError('必须传递brew 方法');};varpourInCup = param.pourInCup ||function(){thrownewError('必须传递pourInCup 方法');};varaddCondiments = param.addCondiments ||function(){thrownewError('必须传递addCondiments 方法');};varF =function(){};F.prototype.init =function(){boilWater();brew();pourInCup();addCondiments();};returnF;};varCoffee = Beverage({brew:function(){console.log('用沸水冲泡咖啡');},pourInCup:function(){console.log('把咖啡倒进杯子');},addCondiments:function(){console.log('加糖和牛奶');}});varTea = Beverage({brew:function(){console.log('用沸水浸泡茶叶');},pourInCup:function(){console.log('把茶倒进杯子');},addCondiments:function(){console.log('加柠檬');}});varcoffee =newCoffee();coffee.init();vartea =newTea();tea.init();复制代码

命令模式

我们把某次任务分成两个部分,一部分程序员实现绘制按钮,他们不知道按钮用来干什么,一部分程序员负责编写点击按钮后的具体行为。

点击按钮1点击按钮2点击按钮3varbutton1 =document.getElementById('button1'),varbutton2 =document.getElementById('button2'),varbutton3 =document.getElementById('button3');varsetCommand =function( button, command ){button.onclick =function(){command.execute();}};varMenuBar = {refresh:function(){console.log('刷新菜单目录');}};varSubMenu = {add:function(){console.log('增加子菜单');},del:function(){console.log('删除子菜单');}};在让button 变得有用起来之前,我们要先把这些行为都封装在命令类中:varRefreshMenuBarCommand =function( receiver ){this.receiver = receiver;};RefreshMenuBarCommand.prototype.execute =function(){this.receiver.refresh();};varAddSubMenuCommand =function( receiver ){this.receiver = receiver;};AddSubMenuCommand.prototype.execute =function(){this.receiver.add();};varDelSubMenuCommand =function( receiver ){this.receiver = receiver;};DelSubMenuCommand.prototype.execute =function(){console.log('删除子菜单');};varrefreshMenuBarCommand =newRefreshMenuBarCommand( MenuBar );varaddSubMenuCommand =newAddSubMenuCommand( SubMenu );vardelSubMenuCommand =newDelSubMenuCommand( SubMenu );setCommand( button1, refreshMenuBarCommand );setCommand( button2, addSubMenuCommand );setCommand( button3, delSubMenuCommand );复制代码

上一篇 下一篇

猜你喜欢

热点阅读