当前位置: 首页 > 科技观察

前端工程师必知的Javascript设计模式

时间:2023-03-20 17:38:02 科技观察

前言设计模式是一个程序员进阶进阶的必备技能,也是衡量一个工程师工作经验和能力的试金石。设计模式是程序员多年经验的浓缩和综合总结,可以更大程度的优化代码,合理重构已有代码。作为一名合格的前端工程师,学习设计模式是总结和反思自己工作经验的另一种方式,也是开发高质量、高可维护性和可扩展性代码的重要手段。我们熟悉的几大框架,jquery、react、vue,其内部也使用了大量的设计模式,如观察者模式、代理模式、单例模式等。因此,作为架构师,设计模式必须被掌握。在中高级前端工程师的面试过程中,面试官也会适当考察求职者对设计模式的理解程度。因此,笔者结合多年的工作经验和学习探索,总结并绘制了javascript设计模式的思维导图和实际案例,让我们一起探索学习。你将获得单例模式、构造器模式、建造者模式、代理模式、外观模式、观察者模式、策略模式、迭代器模式、文本,我们先来看看概览。设计模式能给我们带来什么?上面作者主要总结了几点设计模式可以给工程带来的好处,比如代码解耦、可扩展性、可靠性、有序性和可重用性。让我们来看看我们的第一个JavaScript设计模式。1.单例模式1.1概念解释单例模式:保证一个类只有一个实例,一般先判断该实例是否存在,如果存在则直接返回,如果不存在则先创建再创建然后返回它,这样可以保证一个类只有一个实例对象。1.2功能模块之间的通信。保证某一类对象的唯一性。防止变量污染。1.3注意事项正确使用。闭包容易造成内存泄漏,所以要及时清除不需要的变量。创建一个新对象是昂贵的。1.4实际案例单例模式广泛应用于不同的编程语言中。广泛应用于实际软件应用中,如电脑任务管理器、回收站、网站计数器、多线程线程池设计等。1.5代码实现(function(){//养鱼游戏letfish=nullfunctioncatchFish(){//如果鱼存在,直接返回if(fish){returnfish}else{//如果鱼不存在,得到它Fishreturnsfish=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.2角色创建特定类型的对象。逻辑和业务的封装。2.3注意事项注意划分业务逻辑的边界。配合单例实现初始化等工作。构造函数命名约定,首字母大写。新对象的成本,将公共方法放在原型链上。2.4实战构造函数模式我认为是代码的模式,也是用来检验程序员对业务代码的理解程度。常用于实现javascript工具库,如lodash、javascript框架等。2.5代码展示functionTools(){if(!(thisinstanceofTools)){returnnewTools()}this.name='jstoollibrary'//获取dom的方法this.getEl=function(elem){returndocument.querySelector(elem)}//判断是否为数组this.isArray=function(arr){returnArray.isArray(arr)}//其他常用方法...}3.Builder模式3.1概念解释Builder模式:通过有序的分工,逐步实现一个复杂的逻辑或功能。3.2角色分配创建复杂对象或实现复杂功能。解耦封装过程,不用关注具体创建的细节。3.3防范措施需要有可靠的算法和逻辑支持。根据需要公开某些接口。3.4实际案例建造者模式实际应用在很多领域。笔者之前写过很多js插件,大部分都是采用builder模式。可以到作者的github地址许小希的github学习。其他情况如下:jqueryAjax封装。jquery插件包。react/vue的一个具体组件的设计。3.5代码展示笔者举了一个之前使用builder模式实现的案例:Canvas入门javascript面向对象实现一个图文验证码,接下来让我们使用builder模式实现一个很常见的验证码插件!//canvas绘制图形验证码(function(){functionGcode(el,option){this.el=typeofel==='string'?document.querySelector(el):el;this.option=option;this.init();}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);这.drawText(ctx,文本,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})})()复制代码;//CallnewGcode('#canvas_code',{lineNum:6})4.代理模式4.1概念解释代理模式:一个对象通过某种代理方式控制对另一个对象的访问4.2远程代理(对象本地代理)的作用到另一个对象)。VirtualProxy(对于渲染网络等需要创建成本较大的对象,当页面较大时,可以先使用缩略图代替真实图像)。安全代理(保护对真实对象的访问)。缓存代理(一些开销比较大的操作提供暂存,在下一次操作时,如果传入的参数和之前一样,可以直接返回之前存储的操作结果)。4.3注意事项使用代理会增加代码的复杂度,因此应有选择地使用代理。在实际案例中,我们可以使用代理模式来实现以下功能:通过缓存代理来优化计算性能。图像占位符/骨架屏幕/预加载等。合并请求/资源。4.4代码展示下面通过实现一个计算缓存来谈谈代理模式的应用。//缓存代理函数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.2函数在一定程度上解耦了接口和调用者。创建经典的三层结构MVC。在开发阶段,降低不同子系统之间的依赖和耦合,便于各子系统的迭代和扩展。为大型复杂系统提供清晰的界面。5.3注意事项当外观模式被开发者连续调用时,会造成一定的性能损失,因为每次调用都会进行可用性检测。5.4实际案例我们可以使用外观模式来设计兼容不同浏览器的事件绑定方法和其他需要统一实现接口的方法或抽象类。5.5代码展示下面我们来实现一个兼容不同浏览器的事件监听函数,让大家了解外观模式的使用方法。functionon(type,fn){//对于dom2级别的事件处理程序if(document.addEventListener){dom.addEventListener(type,fn,false);}elseif(dom.attachEvent){//对于IE9以下的浏览器dom.attachEvent('on'+type,fn);}else{dom['on'+type]=fn;}}6.观察者模式6.1概念解释观察者模式:定义了一对多关系中,所有的观察者对象同时监听一个主体对象,当主体对象的状态发生变化时,通知所有的观察者对象,使其自动更新自己.6.2Action目标对象和观察者之间存在动态关系,增加了灵活性。支持简单广播通信,自动通知所有订阅对象。目标对象和观察者之间的抽象耦合关系可以独立扩展和复用。6.3注意事项在观察者模式下,一般需要先监听再触发(特殊情况也可以先发布再订阅,比如QQ的离线模式)。6.4实战案例观察者模式是一个非常经典的设计模式,主要应用如下:系统消息通知。网站日志记录。内容订阅功能。javascript事件机制。react/vue等的观察者6.5代码展示接下来,我们使用原生javascript来实现一个观察者模式:{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收到themessage:-->我是许小希7.StrategyMode7.1StrategyMode概念解读:StrategyMode对不同的算法进行合理的分类和单独封装,在不影响算法使用者的情况下实现不同算法的替换。调用方式相同,降低了使用成本和不同算法之间的耦合度。单独定义算法模型,方便单元测试。避免大量冗余代码判断,如ifelse等。7.3实际案例实现更优雅的表单验证。游戏中的角色得分手。棋盘游戏的输赢算法。7.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实际案例迭代器模式最常见的案例是forEach、map、reduce等数组遍历方法。8.4代码展示接下来笔者用一个自己封装的遍历函数让大家了解迭代器模式的使用。该方法不仅可以遍历数组和字符串,还可以遍历对象。_.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