前言设计模式是一个程序员进阶到高级的必备技能,也是衡量一个工程师工作经验和能力的试金石。设计模式是程序员多年工作经验的浓缩和总结,可以最大限度地优化代码,对现有代码进行合理的重构。作为一名合格的前端工程师,学习设计模式是总结和反思自己工作经验的另一种方式,也是开发高质量、高可维护性和可扩展性代码的重要手段。我们熟悉的几大框架,如jquery、react、vue,也使用了大量的设计模式,如观察者模式、代理模式、单例模式等。因此,作为架构师,设计模式必须是掌握了。在中高级前端工程师的面试过程中,面试官也会适当考察求职者对设计模式的理解,所以笔者结合多年的工作经验和学习探索,总结绘制了一张javascript的思维导图设计模式与实际案例,让我们一起探讨习!你将获得单例模式、构造器模式、建造者模式、代理模式、外观模式、观察者模式、策略模式、迭代器模式、文本,我们先来看看概览。设计模式能给我们带来什么?上面笔者主要总结了几点设计模式可以给项目带来的好处,比如代码解耦、可扩展性、可靠性、有序性和可重用性。接下来,让我们看看我们的第一个javascript设计模式。一、单例模式1.1概念解释单例模式:保证一个类只有一个实例,一般先判断该实例是否存在,存在则直接返回,不存在则先创建再返回它,这样就可以保证一个类只有一个实例对象。1.2模块间通信保证了某个类对象的唯一性,防止变量污染1.3注意事项正确使用这个闭包很可能会造成内存泄漏,所以要及时清除不需要的变量并创建新的对象。创建新对象成本高1.4实际案例单例模式应用广泛在不同的编程语言中,在实际软件应用中有很多应用,如计算机任务管理器、回收站、网站计数器、多线程线程池设计、etc.1.5代码实现(function(){//养鱼游戏letfish=nullfunctioncatchFish(){//如果鱼存在,直接返回if(fish){returnfish}else{//如果鱼不存在,获取钓鱼并返回fish=document.querySelector('#cat')return{fish,water:function(){letwater=this.fish.getAttribute('weight')this.fish.setAttribute('weight',++water)}}}}//每3小时喂一次水setInterval(()=>{catchFish().water()},3*60*60*1000)})()2.构造器模式2.1概念解释构造器模式:用于创建特定类型的对象,以实现业务逻辑和功能的可重用性。2.2Functions创建具体类型的对象逻辑和业务封装2.3注意事项注意划分业务逻辑边界,配合单例实现初始化等工作将public方法放到原型链上2.4实际案例构造函数模式我认为是代码的模式,也用来检验程序员对业务代码的理解程度。常用于实现javascript工具库,如lodash和javascript框架。2.5代码展示functionTools(){if(!(thisinstanceofTools)){returnnewTools()}this.name='js工具库'//获取方法domthis.getEl=function(elem){returndocument.querySelector(elem)}//判断是否为数组this.isArray=function(arr){returnArray.isArray(arr)}//其他通用方法...}3.建造者模式3.1概念解释建造者模式:组合一个复杂的逻辑或功能,通过有序的分工逐步实现。3.2角色分配创建复杂对象或实现复杂功能解耦封装过程,无需关注具体创建细节3.3注意事项需靠靠谱的算法和逻辑支持按需暴露某个接口3.4实际案例建造者模式为实际应用于很多领域。笔者之前写过很多js插件,大部分都是采用builder模式。可以到作者的github地址徐晓曦的github学习。其他情况如下:jquery的ajax封装jquery插件封装react/vue3.5代码展示的具体组件设计builder模式使用案例:Canvas是用javascript面向对象实现一个图形验证码,那我们就用builder模式来实现一个很常见的验证码插件吧!//canvas绘制图形验证码();}Gcode.prototype={constructor:Gcode,init:function(){if(this.el.getContext){isSupportCanvas=true;varctx=this.el.getContext('2d'),//设置画布宽度和高度cw=this.el.width=this.option.width||200,ch=this.el.height=this.option.height||40,textLen=this.option.textLen||4,lineNum=this.option.lineNum||4;vartext=this.randomText(textLen);this.onClick(ctx,textLen,lineNum,cw,ch);this.drawLine(ctx,lineNum,cw,ch);this.drawText(ctx,text,ch);}},onClick:函数(ctx,textLen,lineNum,cw,ch){var_=this;this.el.addEventListener('click',function(){text=_.randomText(textLen);_.drawLine(ctx,lineNum,cw,ch);_.drawText(ctx,text,ch);},false)},//绘制干扰线drawLine:function(ctx,lineNum,maxW,maxH){ctx.clearRect(0,0,maxW,maxH);for(vari=0;i-1){returnthis.generateUniqueText(source,hasList,limit)}else{returntext}}}newGcode('#canvas_code',{lineNum:6})})();//调用newGcode('#canvas_code',{lineNum:6})4.代理模式4.1概念解释代理模式:一个对象通过某种代理方式控制对另一个对象的访问。4.2充当远程代理(一个对象到另一个对象的本地Proxy)VirtualProxy(对于需要创建成本较大的对象,比如渲染网页的大图,可以先用缩略图代替真实图)SecurityProxy(保护真实对象的访问权限)CacheProxy(提供暂存,下一次操作,如果传入的参数和之前一样,可以直接返回之前存储的操作结果)4.3注意事项使用代理会增加代码的复杂性,所以你应该有选择地使用代理。在实际案例中,我们可以使用代理模式实现以下功能:通过缓存代理优化计算性能图片占位符/骨架屏幕/预加载合并请求/资源4.4代码展示下面我们通过实现一个计算来谈谈代理模式的应用cache.//缓存代理函数sum(a,b){returna+b}letproxySum=(function(){letcache={}returnfunction(){letargs=Array.prototype.join.call(arguments,',');if(argsincache){returncache[args];}cache[args]=sum.apply(this,arguments)returncache[args]}})()5.外观模式5.1概念解释外观模式(facade):子系统中的一组接口,提供一致的表现,使子系统更易于使用,无需关注内部复杂繁琐的细节。5.2Function对接口和调用者进行一定程度的解耦创建经典的三层结构MVC减少开发阶段不同的子系统各子系统之间的依赖和耦合方便了各子系统的迭代和扩展,为大而复杂的提供清晰的接口系统。将进行可用性测试5.4实际案例我们可以使用外观模式来设计兼容不同浏览器的事件绑定方法和其他需要统一实现接口的方法或抽象类。5.5代码展示接下来我们实现一个兼容不同浏览器的事件监听函数,让大家了解外观模式的使用方法。functionon(type,fn){//用于支持dom2级别的事件处理器attachEvent('on'+type,fn);}else{dom['on'+type]=fn;}}6.观察者模式6.1概念解释观察者模式:定义一对多关系。所有观察者对象同时监听一个主题对象。当主体对象状态发生变化时,所有的观察者对象都会得到通知,从而自动更新自己。6.2作用于目标对象与观察者之间存在动态关系,增加了灵活性,支持简单的广播通信,自动通知所有订阅对象。目标对象和观察者之间的抽象耦合关系可以单独扩展和重用。6.3笔记观察者模式general注意先监听,再触发(特殊情况也可以先发布再订阅,比如QQ的离线模式)6.4实际案例观察者模式是一个非常经典的设计模式,其主要应用如下:系统消息通知网站日志记录内容订阅函数javascript事件机制react/vue及其他观察者6.5代码展示接下来我们使用原生javascript实现一个观察者模式:classSubject{constructor(){this.subs={}}addSub(key,fn){constsubArr=this.subs[key]if(!subArr){this.subs[key]=[]}this.subs[key].push(fn)}trigger(key,message){constsubArr=this.subs[key]if(!subArr||subArr.length===0){returnfalse}for(leti=0,len=subArr.length;i{console.log('订阅者收到信息:'+message)}subA.addSub('A',A)//发布subA.trigger('A','我是许小希')//A接收到的信息:-->我是许小希7.策略模式7.1概念解释策略模式:策略模式对不同的算法进行了合理的分类和单独封装,使得不同的算法可以相互替换而不影响算法的用户。7.2功能不同,功能一致,调用方式相同,降低了使用成本,降低了不同算法之间的耦合度。单独定义算法模型,方便单元测试,避免大量冗余代码判断,如ifelse等7.3实战案例,实现更优雅的形式验证游戏中的记分员棋牌输赢算法games7.4代码展示接下来我们实现一个模式,根据不同类型实现求和算法,带大家理解策略模式。constobj={A:(num)=>num*4,B:(num)=>num*6、C:(num)=>num*8}constgetSum=function(type,num){returnobj[type](num)}8.迭代器模式8.1概念解释迭代器模式:提供一种顺序访问聚合对象中每个元素的方法,用户无需关心该方法的内部表示。8.2功能为遍历不同的集合提供统一的接口以保护原始集合,同时也提供对内部元素的外部访问。8.3实际案例迭代器模式pattern最常见的案例是forEach、map、reduce等数组的遍历方法。8.4代码展示接下来笔者用自己封装的一个遍历函数,让大家更好的理解迭代器模式的使用。该方法不仅可以遍历数组和字符串,还可以遍历pair像.lodash中的_.forEach(collection,[iteratee=_.identity])方法也是策略模式的典型应用。function_each(el,fn=(v,k,el)=>{}){//判断数据类型functioncheckType(target){returnObject.prototype.toString.call(target).slice(8,-1)}//数组还是字符串if(['Array','String'].indexOf(checkType(el))>-1){for(leti=0,len=el.length;i