先说结论:为什么 Proxy 一定要配合 Reflect 使用?
触发代理对象的劫持时保证正确的 this 上下文指向
我们知道在 Proxy 中(以下我们都以 get 陷阱为例)第三个参数 receiver 代表的是代理对象本身或者继承与代理对象的对象,它表示触发陷阱时正确的上下文。
const parent = {
name: '19Qingfeng',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key);
// 这里相当于 return target[key]
},
};
const proxy = new Proxy(parent, handler);
const obj = {
name: 'wang.haoyu',
};
// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);
// log: false
console.log(obj.value);我们稍微分析下上边的代码:
当我们调用 obj.value 时,由于 obj 本身不存在 value 属性。
它继承的 proxy 对象中存在 value 的属性访问操作符,所以会发生屏蔽效果。
此时会触发 proxy 上的 get value() 属性访问操作。
同时由于访问了 proxy 上的 value 属性访问器,所以此时会触发 get 陷阱。
进入陷阱时,target 为源对象也就是 parent ,key 为 value 。
陷阱中返回
Reflect.get(target,key)相当于target[key]。此时,不知不觉中 this 指向在 get 陷阱中被偷偷修改掉了!!
原本调用方的 obj 在陷阱中被修改成为了对应的 target 也就是 parent 。
自然而然打印出了对应的
parent[value]也就是 19Qingfeng 。
这显然不是我们期望的结果,当我访问 obj.value 时,我希望应该正确输出对应的自身上的 name 属性也就是所谓的 obj.value => wang.haoyu 。
那么,Relfect 中 get 陷阱的 receiver 就大显神通了。
const parent = {
name: '19Qingfeng',
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
- return Reflect.get(target, key);
+ return Reflect.get(target, key, receiver);
},
};
const proxy = new Proxy(parent, handler);
const obj = {
name: 'wang.haoyu',
};
// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);
// log: wang.haoyu
console.log(obj.value);上述代码原理其实非常简单:
首先,之前我们提到过在 Proxy 中 get 陷阱的 receiver 不仅仅会表示代理对象本身同时也还有可能表示继承于代理对象的对象,具体需要区别与调用方。这里显然它是指向继承与代理对象的 obj 。
其次,我们在 Reflect 中 get 陷阱中第三个参数传递了 Proxy 中的 receiver 也就是 obj 作为形参,它会修改调用时的 this 指向。
你可以简单的将
Reflect.get(target, key, receiver)理解成为target[key].call(receiver),不过这是一段伪代码,但是这样你可能更好理解。
相信看到这里你已经明白 Relfect 中的 receiver 代表的含义是什么了,没错它正是可以修改属性访问中的 this 指向为传入的 receiver 对象。