1.写在前面 我们知道Vue2的计实响应式是使用Object.defineProperty来实现的,在实现对象响应式数据比较友好,计实但是计实对于实现数组的响应式数据就存在一些问题。而Vue.js3中的计实对象数据的响应式实现是通过Proxy对原始对象的代理,这样就能够在进行取值和设值操作时进行拦截,计实并对对象数据进行重新定义。计实那么Proxy是计实如何实现代理的呢? Proxy代理就是通过Proxy对一个原始对象进行基本操作的拦截和自定义(如属性查找、赋值、计实枚举、计实函数调用等)。计实 在上面代码片段中,Proxy可以接收两个参数target、计实handler: 那么接下来,我们就来使用下吧。 const data = { name:"pingping", age:18 } const state = new Proxy(data, { //拦截属性取值操作 get(target, key){ //在这里拦截打印数据 console.log(`我的${ key}是:${ target[key]}`); return target[key]; }, //拦截属性设置操作 set(target, key, value){ //在这里拦截设置数据 target[key] = value; console.log(`我的${ key}数据更改为${ target[key]}`); return value } }); state.name; 打印数据为: 控制台打印数据 在上面代码中,我们只用到了get的前两个参数target和target,分别表示代理的原始对象、被获取的属性名。其实Proxy的get方法中还可以传入第三个参数receiver,表示Proxy代理之后的对象或继承Proxy的对象。 target:被代理的原始对象。property:被获取的属性名。receiver:Proxy代理后的对象或者继承Proxy的对象。const p = new Proxy(target, { get: function(target, property, receiver) { } 我们进行个简单的实践: const data = { name:"pingping", age:18 } const state = new Proxy(data, { get: function(target, property, receiver) { console.log(state === receiver); return target[property]; } }); 此时,看到控制台打印的结果是: 控制台打印结果 在上面举的例子中,云南idc服务商的确receiver指的是Proxy代理之后的对象state,当然receiver也可以指向的是继承Proxy的对象。 const data = { name:"pingping", age:18 } const state = new Proxy(data, { get: function(target, property, receiver) { console.log(state === receiver); return target[property]; } }); const obj = { name:"onechuan" } //将obj对象原型设置为state对象,即obj继承state Object.setPrototypeOf(obj, state); 控制台打印结果: 设置继承的打印结果 在上述代码和打印结果中,我们可以看到打印的receiver并不等于state对象。这是因为get方法的第三个参数receiver,可以传递对象get调用者指向,即可以正确传递上下文。 receiver不仅表示Proxy代理后的对象state本身,也会表示继承Proxy的对象。 const data = { name:"pingping", age:18, get value(){ return this.name; } } const state = new Proxy(data, { get: function(target, property, receiver) { console.log(this === receiver);//false console.log(state === receiver);//false console.log(obj === receiver);//true return target[property]; } }); const obj = { name:"onechuan" } //将obj对象原型设置为state对象,即obj继承state Object.setPrototypeOf(obj, state); 我们看到控制台的打印结果如下: 控制台打印结果 我们分析下上面代码,在访问obj.value时,在obj对象上本身是没有value属性的,它会通过原型去查找proxy对象state上的value属性取值器,会去触发state对象上的高防服务器get value()操作符。此时,会触发state对象的取值拦截器,返回target[property]的值,这样使用obj.value取值就变成了data.value,最终返回的结果就是pingping。 具体的Proxy的一些API见MDN文档吧。 在了解了Proxy后,我们再来讨论下Proxy的好兄弟Reflect,它们是如何配合工作的。 Reflect是一个全局内置的对象,它提供拦截JavaScript操作的方法。但是,Reflect本身不是个函数对象,因此其不是一个构造函数,不能使用new进行调用。Reflect的所有属性和方法都是静态的。 那么,我们就使用Reflect来获取对象的属性吧: const state = new Proxy(data, { get: function(target, property, receiver) { console.log(this === receiver);//false console.log(state === receiver);//false console.log(obj === receiver);//true return Reflect.get(target, property); // 等价于return target[property]; } 在控制台可以看到,打印结果是一样的: 控制台打印结果 在这里,使用Reflect.get(target, property)是等价于return target[property]的,this在Proxy.get拦截器中将this的指向了原始数据data对象,这样obj.value打印结果自然也是pingping。 如果我们要获取obj对象自身的name属性,应该怎么办? onst state = new Proxy(data, { get: function(target, property, receiver) { console.log(this === receiver);//false console.log(state === receiver);//false console.log(obj === receiver);//true return Reflect.get(target, property, receiver); // 等价于return target[property]; } 打印结果: 打印结果 我们将Proxy.get的第三个参数receiver传入Reflect.get中,此时我们发现打印结果就变成了onechuan。这是因为Proxy.get的第三个参数receiver,可以表示代理对象state还可以表示继承代理对象state的对象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。 《为什么Proxy一定要配合Reflect使用?》 《MDN文档关于Proxy的描述》 《MDN文档关于Reflect的描述》 《了解学习 Proxy 的好朋友 - Reflect,为什么需要 Reflect》 在Vue.js3中使用Proxy来实现响应式数据,具体就是通过Proxy代理原始对象,通过拦截和修改对象的基本操作。在代理过程中,会出现取值器的this指向问题,此时需要使用Reflect的方法第三个参数receiver来解决。