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

掌握5种常用的前端设计模式,瞬间变高

时间:2023-03-18 10:52:52 科技观察

今天主要介绍一下我们常用的设计模式。大体上有23种设计模式,以及如何在前端使用这些设计模式。接下来主要介绍比较前端中常见的设计模式。设计模式的定义设计模式是面向对象软件设计过程中对特定问题的简明而优雅的解决方案。在不同的编程语言中,设计模式的实现其实可能是不同的。比如java和javascript,在Java这样的静态编译语言中,是不可能动态地给已有的对象添加职责的,所以装饰器模式一般都是通过包装类来实现的。但是在像JavaScript这样的动态解释型语言中,动态地为对象添加职责是非常简单的。这就导致JavaScript语言的装饰器模式不再专注于为对象动态添加职责,而是为函数动态添加职责。本文将介绍以下几种常见的设计模式:工厂模式单例模式代理模式观察者模式策略模式1.工厂模式工厂模式是最常用的创建对象的设计模式,不暴露对象的创建。具体的逻辑,而是把逻辑封装在一个函数中,那么这个函数就可以看成是一个工厂,工厂模式按照抽象程度可以分为:简单工厂、工厂方法和抽象工厂。下面简单举例简单工厂和工厂方法在JavaScript中的应用:1.简单工厂简单工厂模式也叫静态工厂模式。一个工厂对象决定创建某个产品对象类的实例,主要用于创建同一个类对象,比如在实际项目中,我们经常需要根据用户权限渲染不同的页面。有些页面属于高权限的用户,低权限的用户是看不到的,所以我们可以对用户使用不同的权限级别。在的构造函数中,保存用户可以看到的页面。letUserFactory=function(role){functionSuperAdmin(){this.name="超级管理员",this.viewPage=['首页','用户管理','订单管理','应用管理','权限管理']}functionAdmin(){this.name="管理员",this.viewPage=['首页','订单管理','应用管理']}functionNormalUser(){this.name='普通用户',this.viewPage=['Home','OrderManagement']}switch(role){case'superAdmin':returnnewSuperAdmin();break;case'admin':returnnewAdmin();break;case'user':returnnewNormalUser();break;default:thrownewError('参数错误,可选参数:superAdmin,admin,user');}}//调用letsuperAdmin=UserFactory('superAdmin');letadmin=UserFactory('admin')letnormalUser=UserFactory('user')总结:在上面的例子中,UserFactory是一个简单的工厂。在这个函数中,有3个构造函数分别对应不同权限的用户。我们在调用工厂函数的时候,只需要传superAdmin、admin、user即可。选择其中一个参数,获取对应的实例对象。优点:简单工厂的优点是你只需要一个正确的参数就可以得到你需要的对象,而无需知道它创建的具体细节;缺点:函数内部包含了所有对象的创建逻辑(构造函数)和判断逻辑代码。每增加一个新的构造函数,就需要修改判断逻辑代码。当我们的对象不是上面的3个而是30个或者更多的时候,这个函数就会变成一个庞大的超函数,很难维护。简单工厂只能在创建的对象数量少,对象创建逻辑不复杂的情况下使用;推迟到子类,让核心类变成抽象类,但是JavaScript中很难像传统的面向对象那样创建抽象类,所以在JavaScript中,我们只需要参考它的核心思想即可。我们可以把工厂方法看成是实例化对象的工厂类。比如上面的例子,我们可以使用工厂方法这样写。作为实例化对象的工厂,它只为实例化对象做了一件事。我们使用安全模式创建对象//安全模式创建的工厂方法函数letUserFactory=function(role){if(thisinstanceofUserFactory){vars=newthis[role]();returns;}else{returnnewUserFactory(role);}}//设置工厂方法函数原型中所有对象的构造函数UserFactory.prototype={SuperAdmin:function(){this.name="superAdministrator",this.viewPage=['Home','UserManagement','订单管理','应用管理','权限管理']},Admin:function(){this.name="Administrator",this.viewPage=['首页','订单管理','应用管理']},NormalUser:function(){this.name='普通用户',this.viewPage=['首页','订单管理']}}//callletsuperAdmin=UserFactory('SuperAdmin');letadmin=UserFactory('Admin')letnormalUser=UserFactory('NormalUser')总结:在一个简单的工厂中,如果我们添加一个新的用户类型,我们需要修改两个地方的代码,一个是添加一个新的用户构造函数,一个是添加一个新的用户构造函数逻辑判断中的用户判断,以及抽象工厂方法,我们只需要在UserFactory.prototype中添加即可。2、单例模式定义:是保证一个类只有一个实例,并提供一个全局访问点来访问它。需求:我们往往只需要一个对象,比如线程池、全局缓存、浏览器中的window对象、登录悬浮窗等。实现方式:用一个变量来标识某个类是否已经创建了对象。如果是,则在下一次获取该类的实例时,将直接返回之前创建的对象。优点:可以用来划分命名空间,减少全局变量的数量。可以实例化,实例化一次,再次实例化会生成第一个实例。让我们举个例子。在js中,我们可以使用闭包来创建和实现这种模式:varsingle=(function(){varunique;functiongetInstance(){//如果实例存在,直接返回,否则实例化if(unique===undefined){unique=newConstruct();}returnunique;}functionConstruct(){//...生成单例构造函数的代码}return{getInstance:getInstance}})();总结:在上面的代码中,我们可以使用single.getInstance获取到单例,每次调用获取同一个单例,在我们平时的开发中,经常会用到这种模式,比如我们点击登录按钮的时候,页面上会出现一个登录框,而这个悬浮窗唯一性,无论登录按钮被点击多少次,这个浮窗只会创建一次,所以这个登录浮窗适合单例模式。3.代理模式代理模式主要是为其他对象提供一个代理来控制对这个对象的访问,主要解决直接访问对象带来的问题,例如:要访问的对象在远程机器上,在面向对象系统中,由于某些原因(比如对象创建成本高,或者某些操作需要安全控制,或者需要进程外访问),直接访问会给用户或者系统带来很多麻烦结构。我们可以在访问这个对象的时候给这个对象加上一个访问层。代理模式最基本的形式是控制访问。代理对象和另一个对象(本体)实现相同的接口。实际上,工作还是由本体来完成。它是负责执行分配任务的对象或类,代理对象所做的是控制对本体的访问。代理对象不会在另一个对象的基础上增加或修改方法,也不会简化那个对象的接口。它实现的接口与本体完全一致。同样,对其进行的所有方法调用都将传递给本体。(function(){//示例代码//目标对象是实际代理的对象functionSubject(){}Subject.prototype.request=function(){};/***代理对象*@param{Object}realSubject[持有被代理的具体目标对象]*/functionProxy(realSubject){this.realSubject=readSubject;}Proxy.prototype.request=function(){this.realSubject.request();};}());总结:在上面的代码中,Proxy可以控制对真实被代理对象的访问。在代理模式中,比较常见的是虚拟代理。虚拟代理用于控制对创建成本高昂的本体的访问。它会推迟本体的实例化,直到调用一个方法。比如现在我们假设PublicLibrary的实例化很慢,无法在网页加载时立即完成。我们可以为它创建一个虚拟代理,让它使用PublicLibrary虚拟机的实例化推迟到需要的时候。比如我们前端经常用到的图片懒加载,可以作为虚拟代理;4.观察者模式如果你学过vue、react等一些框架,相信大家都对观察者模式不陌生,观察者模式一定很熟悉。现在很多mvvm框架都采用了观察者模式的思想。观察者模式也称为发布-订阅模式。它定义了对象之间一对多的依赖关系。当一个对象的状态发生变化时,一个事件发生时,所有依赖它的对象都会得到通知和更新。观察者模式提供了一种订阅模型,其中对象订阅事件并在事件发生时得到通知。这种模式是事件驱动编程的基石,它有利于良好的面向对象设计定义:对象之间的一对多依赖关系。需求:当一个对象的状态发生变化时,所有依赖它的对象都会得到通知。优点:及时解耦,对象之间解耦。实施:指定谁将充当发布者;向发布者添加一个缓存列表,用于存储通知订阅者的回调函数;发布消息时,发布者会遍历缓存列表,依次触发存储在其中的订阅者的回调函数。让我们举个例子。比如我们给页面中的dom节点绑定一个事件,其实可以看成是观察者模式:document.body.addEventListener("click",function(){alert("HelloWorld")},false)document.body.click()//模拟用户点击总结:在上面的例子中,我们需要监听用户点击document.body的动作,但是我们没有办法预测用户什么时候会点击,所以我们订阅了document.body的点击事件。单击主体节点时,主体节点将向订阅者发布“HelloWorld”消息。5.策略模式策略模式是指定义一系列的算法,并把它们一个一个封装起来。目的是将算法的使用和算法的实现分开,避免多重判断条件,更具可扩展性。下面也是一个例子,现在超市有活动,VIP50折,老顾客30折,普通顾客不打折,计算***需要支付的金额,如果我们不如果不使用strategy模式,我们的代码可能和下面一样:functionPrice(personType,price){//vip5折if(personType=='vip'){returnprice*0.5;}elseif(personType=='old'){//老顾客30%offreturnprice*0.3;}else{returnprice;//其他都是全价}}上面的代码中,我们需要做很多的判断。如果有很多折扣,我们需要添加很多判断。这就违反了刚才说的设计模式六大原则的开闭原则现在,如果我们使用策略模式,我们的代码可以这样写://ForvipcustomersfunctionvipPrice(){this.discount=0.5;}vipPrice.prototype.getPrice=function(price){returnprice*this.discount;}//老客户functionoldPrice(){this.discount=0.3;}oldPrice.prototype.getPrice=function(price){returnprice*this.discount;}//对于普通客户functionPrice(){this.discount=1;}Price.prototype.getPrice=function(price){returnprice;}//上下文,供客户端使用functionContext(){this.name='';this.strategy=null;this.price=0;}Context.prototype.set=function(name,strategy,price){this.name=name;this.strategy=strategy;this.price=price;}Context.prototype.getResult=function(){console.log(this.name+'结账价格为:'+this.strategy.getPrice(this.price));}varcontext=newContext();varvip=newvipPrice();context.set('vipcustomer',vip,200);context.getResult();//vip客户结账价格为:100varold=newoldPrice();context.set('老客户',old,200);context.getResult();//老客户结账价格为:60varPrice=newPrice();context.set('普通客户',Price,200);context.getResult();//普通客户结账价格为:200总结:以上代码中,通过策略模式,将客户的折扣与算法解耦,可以独立进行修改和扩展,不影响客户端或其他算法的使用.代码中有很多判断分支,每一个条件分支都会导致“类”的具体行为发生不同的变化。这时候可以利用策略模式来提高我们代码的质量,同时也更好的进行单元测试。