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

Vue.js设计与实现-Proxy和Reflect

时间:2023-03-12 01:45:15 科技观察

Vue.js设计与实现-Proxy与Reflect♂1.我们知道Vue2的响应式是使用Object.defineProperty实现的。实现对象响应式数据是友好的,但是对于数组响应式数据就有些问题了。Vue.js3中对象数据的响应式实现是通过Proxy对原对象进行代理,使其在进行取值和设置操作时可以拦截并重新定义对象数据。那么Proxy是如何实现代理的呢?2.Proxy代理Proxy代理就是通过Proxy拦截和定制对一个原始对象的基本操作(如属性查找、赋值、枚举、函数调用等)。constp=newProxy(target,handler);在上面的代码片段中,Proxy可以接收两个参数target和handler:target:表示要代理的原始对象(可以是任何类型的对象、函数、数组等)。handler:通常是一个以函数为属性的对象。这个对象是一组陷阱。每个属性中的函数定义代理p在执行各种操作时的行为。那么接下来,让我们使用它。constdata={name:"pingping",age:18}conststate=newProxy(data,{//拦截属性值操作get(target,key){//这里拦截打印数据console.log(`I${key}is:${target[key]}`);returntarget[key];},//拦截设置属性操作set(target,key,value){//拦截设置数据target[keyhere]=value;console.log(`我的${key}数据更改为${target[key]}`);返回值}});状态名称;state.name="onechuan"打印数据为:control在上面的代码中,我们只使用了get的前两个参数target和target,分别代表agent的原始对象和要获取的属性的名称。其实Proxy的get方法也可以传入第三个参数receiver,表示Proxy代理后面的对象或者继承Proxy的对象。target:要代理的原始对象。property:要获取的属性名称。receiver:Proxy代理背后的对象或继承Proxy的对象。constp=newProxy(target,{get:function(target,property,receiver){}});我们来做个简单的练习:constdata={name:"pingping",age:18}conststate=newProxy(data,{get:function(target,property,receiver){console.log(state===receiver);返回目标[属性];}});状态名称;此时,查看控制台打印的结果是:控制台打印结果。在上面的例子中,确实,receiver指的是Proxy代理之后对象的状态。当然receiver也可以指向继承自Proxy的对象。constdata={name:"pingping",age:18}conststate=newProxy(data,{get:function(target,property,receiver){console.log(state===receiver);返回目标[属性];}});constobj={name:"onechuan"}//设置obj对象的原型为state对象,即obj继承stateObject.setPrototypeOf(obj,state);对象名称;consoleprintresult:setinherited打印结果在上面的代码和打印结果中,我们可以看到打印的receiver不等于state对象。这是因为receiver,get方法的第三个参数,可以传递get调用者指向的对象,也就是可以正确传递context。receiver不仅代表Proxy代理后的对象状态本身,还代表继承Proxy的对象。constdata={name:"pingping",age:18,getvalue(){returnthis.name;}}conststate=newProxy(data,{get:function(target,property,receiver){console.log(this===receiver);//falseconsole.log(state===receiver);//falseconsole.log(obj===receiver);//truereturntarget[property];}});constobj={name:"onechuan"}//设置obj对象原型为状态对象,即obj继承stateObject.setPrototypeOf(obj,state);对象值;我们看到控制台打印结果如下:consoleprintresults我们分析上面的代码,在访问obj.value的时候,obj对象本身是没有value属性的,它会使用prototype去state对象上找value属性的getter代理对象,并将触发stateobject()运算符上的获取值。这时会触发state对象的值拦截器,返回target[property]的值,这样使用obj.value得到的值就变成了data.value,最终返回的结果是pingping。请参阅特定代理API的MDN文档。3.Reflect了解了Proxy之后,我们来讨论一下Proxy的好兄弟Reflect,以及它们是如何协同工作的。Reflect是一个全局内置对象,提供拦截JavaScript操作的方法。但是,Reflect本身不是函数对象,因此它不是构造函数,不能使用new调用。Reflect的所有属性和方法都是静态的。reflect.get(target,propertyKey[,receiver])target:需要获取值的目标对象propertyKey:需要获取值的键值receiver:如果目标对象中指定了getter,则接收者是调用getter时的this值那么,我们用Reflect来获取对象的属性:conststate=newProxy(data,{get:function(target,property,receiver){console.log(this===receiver);//falseconsole.log(state===receiver);//falseconsole.log(obj===receiver);//truereturnReflect.get(target,property);//相当于returntarget[财产];}});在control中可以看到打印结果是一样的:控制台打印结果在这里,使用Reflect.get(target,property)相当于returntarget[property],而proxy.get拦截器中this指向this的原始data数据对象创建好了,那么obj.value的打印结果自然是pingping的。如果我们想获取obj对象本身的name属性怎么办呢?onststate=newProxy(data,{get:function(target,property,receiver){console.log(this===receiver);//falseconsole.log(state===receiver);//false控制台。log(obj===receiver);//truereturnReflect.get(target,property,receiver);//相当于returntarget[property];}});printresult:printresult我们把Proxy.get的第三个参数receiver传入Reflect.get,发现打印结果变成了onechuan。这是因为Proxy.get的第三个参数receiver可以表示代理对象状态或者继承代理对象状态的对象obj。在Reflect.get中,传入了Proxy.get的第三个参数receiver,即obj对象作为参数。这时候Reflect.get会将this的指向改为obj。Reflect.get(target,key,receiver)其实可以理解为target[key].call(receiver),Reflect.get的参数receiver的作用:修改属性访问时,this指向receiver。4.参考文章《为什么Proxy一定要配合Reflect使用?》《MDN文档关于Proxy的描述》《MDN文档关于Reflect的描述》《了解学习 Proxy 的好朋友 - Reflect,为什么需要 Reflect》5。写在最后,利用Vue.js3中的Proxy来实现响应式数据,具体来说,就是通过Proxy代理原始对象,拦截修改对象的基本操作。在代理过程中,valuegetter的this点会出现问题。这时候就需要用到Reflect方法的第三个参数receiver来解决了。