前言
先说结论,Proxy 与 Reflect 结合使用是为了在触发 proxy 的时候纠正 this 的指向问题
了解过 Proxy 的同学都知道,Proxy 会拦截一个对象的 get 和 set 方法,并且返回一个新的对象,我们去修改这个新对象就会触发 get 和 set 方法。
那么由于存在两个不同的对象,那么是不是就会存在两个不同的this,这在某些时候会有一些意料之外的情况发生。
Proxy
首先先来简单使用一下 Proxy
js
const text = {
name: 'xiaomi',
}
const proxy = new Proxy(text, {
get(target, key) {
console.log('🚀 触发拦截')
return target[key]
},
})
proxy.name
// 🚀 触发拦截
在访问 proxy 的属性的时候触发了拦截,target表示原对象,key表示访问的key
但是 get 方法还存在 第三个参数 receiver
接下来我们举两个例子来看一下意外情况
意外情况
1
先了解一下 js 原生对象中的 get set 这个结合 proxy 会造成一些理想外的情况
js
const text = {
name: 'xiaomi',
get value() {
return this.name
},
}
const text2 = {
name: 'xiaohong',
}
const proxy = new Proxy(text, {
get(target, key, receiver) {
return target[key]
},
})
Object.setPrototypeOf(text2, proxy)
console.log(text2.value) // xiaomi
text2 继承了 proxy,并且因为 text2 身上没有 value 属性,所以一旦访问 value 就会触发陷阱,但是你要是访问 name 因为 text2 身上有这个属性,所以不会向父元素查找,就不会触发陷阱。同理,如果 obj 身上有 value 属性,那就不会触发陷阱。
所以你可以看到,虽然我们触发了 proxy 的 get 陷阱,但是 target 对象是 text,所以 this.name 就会得到 text 的name 也就是 xiaomi。这显然不是我们想要的,访问了 text2 那么this.name 就希望是 text2 的name,也就是 xiaohong
2
另一种情况,当我们访问的这个 get 属性有去 访问当前对象的其他属性的时候,按道理来说应该继续触发 proxy 的拦截
js
const p1 = {
name: '张',
get fullName() {
return this.name
},
}
const proxy = new Proxy(p1, {
get(target, key, receiver) {
console.log('触发了 getter')
return target[key]
},
})
console.log(proxy.fullName)
但是上面这个例子,就只会触发一次拦截,这也是因为,在 return target[key]
触发 fullName 的时候获取到的 this 为 p1。
从上面两个例子可以发现,在 proxy 的 get 方法当中,在一些特殊情况我们获取到的this并不是我们想要的,所以这时候就轮到 receiver 登场
receiver
get(target, key, receiver)
get 中的第三个参数是 receiver 这个属性代表当前触发这个陷阱的上下文,比如第一个例子中的 receiver 就是继承了text 的 text2 对象
js
const text = {
name: 'xiaomi',
get value() {
return this.name
},
}
const text2 = {
name: 'xiaohong',
}
const proxy = new Proxy(text, {
get(target, key, receiver) {
console.log('🚀', receiver === text) // false
console.log('🚀', receiver === text2) // true
return target[key]
},
})
Object.setPrototypeOf(text2, proxy)
console.log(text2.value) // xiaomi
那么我们在这时候就又可以引出 Reflect 对象,这个对象的get方法作用是跟 target[key] 相同的,唯一的不同点就是 ,它也可以传入第三个参数 receiver
js
target[key]
Reflect.get(target, key, receiver)
Reflect.get 的第三个参数 receiver 可以修改 this 指向
那么在这时候我们就能够得到正确的返回
js
const text = {
name: 'xiaomi',
get value() {
return this.name
},
}
const text2 = {
name: 'xiaohong',
}
const proxy = new Proxy(text, {
get(target, key, receiver) {
console.log('🚀', receiver === text) // false
console.log('🚀', receiver === text2) // true
return Reflect.get(target, key, receiver)
},
})
Object.setPrototypeOf(text2, proxy)
console.log(text2.value) // xiaohong
对于第二个例子也是一样的,会触发两次 get 陷阱,感兴趣的伙伴可以自己尝试一下
总结
在阅读 vue3 源码的过程一定会碰到 Proxy 与 Reflect 相关的问题,这里结合大佬们的文章记录了一下自己的理解,如果有写的不好的地方欢迎评价。