当前位置: 首页 > Web前端 > JavaScript

认识Proxy

时间:2023-03-27 12:24:07 JavaScript

最近在复习Vue,难免接触到vue3,所以难免会思考这些问题。为什么用proxy代替vue3中的Object.defineProperty来实现响应式?Proxy相对于Object.defineProperty有什么优缺点?如何通过Proxy实现响应式?本文将回答这两个问题,通过这些问题讨论Proxy,以及Proxy在日常开发中的应用场景。理解ProxyProxy翻译过来就是代理,外部对目标对象的访问会被Proxy拦截,从而实现基础操作的拦截和定制。用法letproxy=newProxy(target,handler)target:要拦截的目标对象handler:handler是一个包含了你要拦截和处理的对象的对象,当对象被代理时,handler通过陷阱实现各种行为拦截当前proxy支持13种行为拦截处理方法whentotriggergetreadpropertysetwritepropertyhasinoperatordeletePropertydeleteoperatorapplyfunctioncallconstructnewoperatorgetPrototypeOfObject.getPrototypeOfsetPrototypeOfObject.setPrototypeOfisExtensibleObject.isExtensiblepreventExtensionsObject.preventExtensionsdefinePropertyObject.definePropertyPropertyPropert.Object,Object.getOwnProperty.Descriptor,for..in,Object.keys/values/entriesownKeysObject.getOwnPropertyNames,Object.getOwnPropertySymbols,for...in,Object.keys/values/entriesReflectreflect翻译过来就是映射的意思,在MDN上定义Reflect是一个内置对象,它提供交互方法接受JavaScript操作。每个可用的代理陷阱(trap)都有一个对应的同名Reflect函数并产生相同的行为。letobj={a:10,name:'oyc'}letnewTarget=newProxy(obj,{set(target,key,val){console.log(`Set${key}=${val}`);}})//newTarget.a=20;//设置a=20//Reflect.set(newTarget,'a',20);//设置a=20newTarget.name='oyq';//设置名称=oyqReflect.set(newTarget,'name','oyq');//Setname=oyq从这里可以看出Reflect和trap的表现是一样的。所以当你为如何触发陷阱而苦恼时,或许这篇Reflect可以帮到你。两个问题在大致了解了代理的内容后,尝试回答下一页标题中提到的两个问题。为什么vue3使用代理而不是Object.defineProperty来实现响应式?的优点和缺点?优点:性能更好,Object.defineProperty只能劫持对象的属性,所以如果有嵌套对象,初始化时需要遍历data中的每个属性。vue3中proxy可以代理对象,不需要像vue2那样修改属性遍历操作//vue2functionreactive(obj){//遍历对象for(constiteminobj){if(obj[item]&&typeofobj[item]=='object'){//递归,遍历reactive(obj[item])}else{defineReactive(obj,item,obj[item])}}}functiondefineReactive(obj,key,val){Object.defineProperty(obj,key,{//set,getoperation})}//vue3letnewTarget=newProxy(obj,{//set,getoperation})自动代理新的属性,数组,Object.defineProperty的实现是劫持属性,所以当有新的属性加入时,需要重新遍历,重新劫持新加入的属性。所以vue2需要$set新的属性和数组来保证属性是响应式的。这个过程是手动的。letobj={a:10,name:'oyc'}//vue2this.$set(this.obj,'age',18);//每次新增都需要这个操作//vue3//autoproxyletnewTarget=newProxy(obj,{get(target,key){returnReflect.get(target,key);},set(target,key,val){returnReflect.set(target,key,val);}})Proxy在13中支持拦截操作,Object.defineProperty是无与伦比的。Proxy是新标准,以后会优化。Object.defineProperty的setter和getter以后应该优化一下。缺点很明显。Proxy对Object.defineProperty的兼容性比较低,不支持IE浏览器。不过从目前的市场份额来看,IE浏览器的市场份额并不多。目前微软也已经将IE换成了chrome内核的edge,这样比较激进的项目就可以使用proxy了。如何通过Proxy实现响应式?letobj1={a:10,name:'John',list:[1,2,3],obj2:{obj3:'oo',obj4:{name:'oyc'}}}//判断是否是一个对象constisObj=(obj)=>typeofobj==='object'&&obj!==null;constrender=(key,val)=>{console.log(`Render${key}=${val}`);}functionreactive(obj){if(!isObj(obj)){returnobj;}consthandler={get(target,key){//遍历嵌套对象if(isObj(target[key])){//递归returnreactive(target[key]);}returnReflect.get(target,key);},set(target,key,val){//渲染render(key,val);returnReflect.set(目标,键,值);}}consttargetProxyObj=newProxy(obj,handler);返回targetProxyObj}让myObj=reactive(obj1);myObj.a=20;//渲染a=20myObj.b=30;//新属性Renderb=30myObj.list=[1,2,5,6];//修改号Group//渲染列表=1,2,5,6myObj.obj2.obj4.name='oyq';//修改嵌套对象//Rendername=oyqProxy应用场景写默认值,日常开发经常遇到ReferenceError:xxxisnotdefined这种错误,这里我们可以代理,当属性不存在时,不报错,但是设置了默认值}else{return'OYC';}},})console.log(proxyObj.age);//OYC使用Proxy封装fetch,让fetch更易用lethandlers={get(target,property){if(!target.init){//初始化对象['GET','POST'].forEach(method=>{target[method]=(url,params={})=>{returnfetch(url,{headers:{'content-type':'application/json'},mode:'cors',credentials:'same-origin',method,...params}).then(response=>response.json())}})}returntarget[property]}}letAPI=newProxy({},handlers)awaitAPI.GET('XXX')awaitAPI.POST('XXX',{body:JSON.stringify({name:1})})检验表letformData={name:'',age:''}letproxyObj=newProxy(formData,{set(target,key,val){if(key==='age'&&typeofval!=='number'){console.log('agemustbenumber');}if(key==='name'&&typeofval!=='string'){console.log('namemustbestring');}}})proxyObj.age='oyc';//age必须是numberproxyObj.name=18;//name必须是字符串负索引数组letarr=[1,2,3,4,5]letproxyArray=newProxy(arr,{get(target,key){constindex=key<0?target.length+Number(key):key;returnReflect.get(target,index)}})console.log(proxyArray[-1]);//5