浅谈vue3的Proxy和Reflect

299 阅读4分钟

查看官方文档的时候,有一部分不太能理解

image.png 参考了MDNProxy以及Reflect之后,get到这个点,以下是我的个人见解:

---Proxy的handle.get(target, property[, receiver])---

let obj = { a:1 }
var p = new Proxy(obj, {
  get: function(target, property, receiver) {
      console.log(target) // {a:1}
      console.log(property) // 'a'
      return  receiver
  }
});
console.log(p.a) // Proxy {a:1}

其中三个参数:targetpropertyreceiver

1、target:顾名思义:源目标对象

2、property:属性名,例子中为'a'

3、receiver:是一个Proxy类型的对象或者继承Proxy的对象(注意,这个对象区别于target目标对象)或者是Reflect.get第三个参数指定的值

如何理解这里的继承Proxy的对象,看下面的例子:

let obj = { a:1 }
var p = new Proxy(obj, {
  get: function(target, property, receiver) {
      console.log(target) // {a:1}
      console.log(property) // 'a'
      return  receiver
  }
});

let p2 = Object.create(p) //以p为原型,创造一个对象p2

console.log(p.a) // Proxy {a:1}       1/Proxy类型的对象
console.log(p2.a) // {}               2/继承Proxy的对象,即p2这个空对象
Reflect.get(p,'a','asd') // 'asd'     3/Reflect.get第三个参数指定的值

其中有个Reflect.get,后面会说到。

---Reflect.get(target, property[, receiver])---

1、背景: Reflect是一个挺有用的方法,具体有哪些api可以参照MDN,可以打开新世界大门~~

2、方法解释: 这里的get方法,其实就是对象取值的一种装逼写法。

let obj = { a:1 }
Reflect.get(obj,'a') // 1 其实就是 obj.a

tips: 但是除了装逼的取值作用,还有一个挺有用的参数,那就是receiver,下面详细讲解:

3、何谓receiver

MDN给出的解释是:

如果target对象中指定了getterreceiver则为getter调用时的this值。

这样说有点抽象,直接上代码(解释在注释中):

let obj = {
    a: 2,
    get a(){ // `target`对象中指定了`getter`
        return this.b
    },
    c:'c'
}
console.log(obj.a) // undefined
console.log(Reflect.get(obj, 'a')) // undefined  因为Reflect.get(obj,'a')===obj.a
console.log(Reflect.get(obj, 'a', {b: 'hhh'})) // 'hhh' 解释`receiver`则为`getter`调用时的`this`值
                                               // 等价于obj.a,但是因为a具有getter,于是走getter函数,取的是this.b所以为第三个参数的'hhh'
console.log(Reflect.get(obj,'c','hhh')) // 'c' 并不影响参数1[参数2],即obj['c']的取值,影响的为this指向
                                        // 等价于obj.c

---前两者的receiver结合---

前面提到:Proxy.get的第三个参数会是Reflect.get第三个参数指定的值,即:

let obj = { a:1 }
var p = new Proxy(obj, {
  get: function(target, property, receiver) {
      return  receiver
  }
});
console.log(Reflect.get(p, 'a', 'hhh')) // 'hhh'

所以对于上面MDNReflect.getreceiver参数的解释,我认为还需要加上一个,即可以影响Proxyhandler.get中的receiver的取值

问题来了,要是原对象有getter,代理又有getter会产生出哪些火花呢

答:各走各的路线,注意区分代理对象和源对象,已经为两个对象了,唯一的联系在回调的target参数里。

直接上代码:

let obj = { 
    get a(){ 
        return this.b
    },
    b: 'b',
    c: 'c'
}
var p = new Proxy(obj, {
  get: function(target, property, receiver) {
      return  receiver
  }
})
console.log(p.a) // Proxy {b: 'b', c: 'c'}
console.log(obj.a) // 'b'

当代理中,使用了target相关的逻辑,则有些许变化了:

let obj = { 
    get a(){ 
        return this.b
    },
    b: 'b',
    c: 'c'
}
var p = new Proxy(obj, {
  get: function(target, property, receiver) {
      return  receiver
  }
})
let p2 = new Proxy(obj, {
  get: function(...args) {
      return  Reflect.get(...args) // 等价于使用了target[property],与源对象obj产生了联系
  }
})
console.log(p2.a) // 'b'

注意其中的Reflect.get(...args),等价于使用了target[property],于是与源对象obj产生了联系。

分析下运行流程:

1、console.log(p2.a)

2、进入代理对象p2get函数中

3、执行Reflect.get(target,property,receiver),其中targetobjproperty为'a',receiverp2这个代理对象

4、第3步等价于执行obj['a'],并且将objagetter方法中的this置为p2这个代理对象

5、进入obja属性的getter方法

6、执行return this.b,由于第3、4步中重置了this为p2代理对象

7、等价于执行 return p2.b

8、继续进入p2get方法

9、执行Reflect.get(target,property,receiver),其中targetobjpropertybreceiverp2这个代理对象

10、第9步等价于执行obj['b'],并且将objbgetter方法中的this置为p2这个代理对象

11、obj中的b属性并没有getter拦截,所以直接取出值'b',返回出来

---再次回到开头提到的vue3部分---

根据上面的执行流程,可以总结出,通过

const dinner = {
  meal: 'tacos'
}

const handler = {
  get(target, property, receiver) {
    return Reflect.get(...arguments)
  }
}

const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)

return Reflect.get(...arguments)写法,实现让所有源对象中的拦截的this,指向vue给响应数据创建的代理对象,进而实现将任何方法都绑定到这个Proxy,而不是目标对象,统一进行一些响应式捕获处理的操作