当前位置: 首页 > 后端技术 > Node.js

从ES6重新认识JavaScript设计模式(五):代理模式与Proxy

时间:2023-04-03 12:54:40 Node.js

1什么是代理模式?为其他对象提供代理以控制对此对象的访问。在某些情况下,一个对象不适合或不能直接引用另一个对象,代理对象可以充当客户端和目标对象之间的中介。在生活中,代理模式的场景非常普遍。比如我们现在有租房或者买房的需求,我们往往会去找链家这样的房屋中介,而不是直接去找想卖房或者租房的人。这时候,链家扮演的角色就是代理人。链家及其所代表的客户可能会提供相同的租赁和出售房屋的方式(收款、签约),但链家作为代理人提供了访问限制,使我们无法直接访问所代理的客户。在面向对象编程中,代理模式的合理使用可以很好地体现以下两个原则:单一职责原则:在面向对象设计中,鼓励将不同的职责分配给细粒度的对象,Proxy是在原有基础上目的。在不影响原有对象的情况下派生出功能,符合松耦合高内聚的设计理念。开闭原则:在不修改代码其他部分的情况下,可以随时将代理从程序中移除。在实际场景中,随着版本的迭代,可能有多种原因不再需要agent,所以可以很方便的去掉。代理对象替换为原来的对象调用2ES6中的Proxy模式ES6提供的Proxy构造函数让我们可以很方便的使用代理模式:varproxy=newProxy(target,handler);Proxy构造函数传入两个参数,第一个参数target代表被代理的对象,第二个参数handler也是一个对象,用来设置被代理对象的行为。想知道Proxy的具体用法,可以参考阮一峰的《 ECMAScript入门 - Proxy 》。本文将使用Proxy来实现前端三种代理模式的使用场景,分别是:缓存代理、验证代理、私有属性的实现。2.1缓存代理缓存代理可以缓存一些昂贵方法的运行结果。再次调用该函数时,如果参数一致,可以直接返回缓存中的结果,无需重新计算。比如使用后端分页的表格,每次页码变化都需要重新请求后端数据。我们可以缓存页码和相应的结果。请求同一个页面时,不需要执行ajax请求,直接返回缓存。数据。下面我们假设在没有任何优化的情况下计算斐波那契数列的函数是一种非常昂贵的方法。这个递归调用在计算40以上的Fibonacci项时可以明显感受到延迟。constgetFib=(number)=>{if(number<=2){返回1;}else{returngetFib(number-1)+getFib(number-2);}}现在让我们编写一个用于创建缓存代理的工厂函数:constgetCacheProxy=(fn,cache=newMap())=>{returnnewProxy(fn,{apply(target,context,args){constargsString=args.join('');if(cache.has(argsString)){//如果有缓存,直接返回缓存数据console.log(`输出${args}的缓存结果:${cache.get(argsString)}`);returncache.get(argsString);}constresult=fn(...args);cache.set(argsString,result);returnresult;}})}调用方法如下:constgetFibProxy=getCacheProxy(getFib);getFibProxy(40);//102334155getFibProxy(40);//输出40的缓存结果:102334155当我们第二次调用getFibProxy(40)时,并没有调用getFib函数,而是直接从缓存中返回之前缓存的计算结果。通过添加缓存代理,getFib只需要专注于自己负责计算斐波那契数列,缓存功能由Proxy对象实现。这实现了我们前面提到的单一职责原则。2.2验证代理Proxy构造函数的第二个参数中的set方法可以轻松验证传递给对象的值。让我们以传统的登录表单为例。表单对象有两个属性,分别是账号和密码。每个属性值都有一个简单的验证方法,对应于它的属性名。验证规则如下://表单对象constuserForm={account:'',password:'',}//验证方法constvalidators={account(value){//account只允许是中文constre=/^[\u4e00-\u9fa5]+$/;return{valid:re.test(value),error:'"account"只允许是中文'}},password(value){//密码长度要大于6个字符return{valid:value.length>=6,error:'"password"shouldmorethan6character'}}}让我们使用Proxy来实现一个通用的表单验证器constgetValidateProxy=(target,validators)=>{returnnewProxy(target,{_validators:validators,set(target,prop,value){if(value===''){console.error(`"${prop}"不允许为空`);returntarget[prop]=false;}constvalidResult=this._validators[prop](value);if(validResult.valid){returnReflect.set(target,prop,value);}else{console.error(`${validResult.error}`);返回目标[prop]=false;}}})}调用方法如下constuserFormProxy=getValidateProxy(userForm,validators);userFormProxy.account='123';//"account"只允许是ChineseuserFormProxy.password='he';//"password"shouldmorethan6characters我们调用getValidateProxy方法生成一个代理对象userFormProxy,在设置属性时会根据验证器的验证规则来验证该值。我们使用console.error来抛出错误信息。当然,我们也可以在DOM中添加事件,实现页面中的验证提示。2.3私有属性代理模式的实现另一个非常重要的应用是访问限制的实现。众所周知,JavaScript没有私有属性的概念。通常,私有属性是通过函数作用域中的变量实现的。虽然实现了私有属性,但不利于可读性。私有属性通常以下划线开头。通过Proxy构造函数中第二个参数提供的方法,我们可以很好的限制对以_开头的属性的访问。接下来,我将实现getPrivateProps函数。该函数的第一个参数obj是要代理的对象,第二个参数filterFunc是过滤访问属性的函数。目前,该函数的作用是限制对以_开头的属性的访问。functiongetPrivateProps(obj,filterFunc){returnnewProxy(obj,{get(obj,prop){if(!filterFunc(prop)){letvalue=Reflect.get(obj,prop);//如果它是一个方法,将this指向修改原对象prop)){thrownewTypeError(`无法设置属性"${prop}"`);}returnReflect.set(obj,prop,value);},has(obj,prop){returnfilterFunc(prop)?false:Reflect.has(obj,prop);},ownKeys(obj){returnReflect.ownKeys(obj).filter(prop=>!filterFunc(prop));},getOwnPropertyDescriptor(obj,prop){returnfilterFunc(prop)?undefined:Reflect.getOwnPropertyDescriptor(obj,prop);}});}functionpropFilter(prop){returnprop.indexOf('_')===0;}在上面getPrivateProps方法的内部实现,第一个第二个参数,我们使用了提供的get,set,has,ownKeys,getOwnPropertyDescripto其实这些方法的作用就是最大程度的限制私有属性的访问。在get方法里面,我们有一个判断。如果访问对象方法,则this指向代理对象。使用Proxy需要注意。如果不这样做,方法内部的this会指向Proxy代理。我们来看一下getPrivateProps的调用方法,验证一下其代理提供的访问控制能力。constmyObj={public:'hello',_private:'secret',method:function(){console.log(this._private);}},myProxy=getPrivateProps(myObj,propFilter);console.log(JSON.stringify(myProxy));//{"public":"hello"}console.log(myProxy._private);//undefinedconsole.log('_private'inmyProxy);//falseconsole.log(Object.keys(myProxy));//["public","method"]for(letpropinmyProxy){console.log(prop);}//公共方法myProxy._private=1;//UncaughtTypeError:Can'tsetproperty"_private"3总结ES6提供的Proxy让JS开发者可以很方便的使用代理模式。听说Vue3.0也会用Proxy重写大量核心代码。代理模式虽然很方便,但是在业务开发中还是要注意使用场景。在写对象的时候不需要提前猜测是否需要使用代理模式。只有当对象的功能变得复杂或者我们需要实现一定的访问限制时,才考虑使用代理。参考文献[1]掘金:使用Javascript原生Proxy优化应用[2]DealWithJS:ES6Features-10UseCasesforProxy[3]曾经探索过的JavaScript设计模式和开发实践[M].r人民邮电出版社