为什么Proxy和Reflect要配合使用

主要对于Proxy和Reflect的get方法做一些演示,来说明为什么Proxy和Reflect要配合使用

文章内容主要来源于公众号前端大全。当作笔记记录。如有侵扰烦请联系删除。

还是之前说过的一点:

Reflect 里的函数都和 Proxy 的陷阱配套,返回值的类型也和 Proxy 要求的相同,所以如果我们要实现原本的功能,直接调用 Reflect 里对应的函数就好了。

这里主要对于Proxy和Reflect的get方法做一些演示,来说明为什么Proxy和Reflect要配合使用。

  • Proxy代理,它内置了一系列”陷阱“用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
  • Reflect反射,它提供拦截 JavaScript 操作的方法。这些方法与 Proxy [5]的方法相同。

简单来说,我们可以通过 Proxy 创建对于原始对象的代理对象,从而在代理对象中使用 Reflect 达到对于 JavaScript 原始操作的拦截。

单独使用Proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
name: 'wang.haoyu',
};

const proxy = new Proxy(obj, {
// get陷阱中target表示原对象 key表示访问的属性名
get(target, key) {
console.log('劫持你的数据访问' + key);
return target[key]
},
});

proxy.name // 劫持你的数据访问name -> wang.haoyu

当访问我们访问 proxy.name 时实际触发了对应的 get 陷阱,它会执行 get 陷阱中的逻辑,同时会执行对应陷阱中的逻辑,最终返回对应的 target[key] 也就是所谓的 wang.haoyu

Proxy中的receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
name: 'wang.haoyu',
};

const proxy = new Proxy(obj, {
// get陷阱中target表示原对象 key表示访问的属性名
get(target, key, receiver) {
console.log(receiver === proxy);
return target[key];
},
});

// log: true
proxy.name;

我们在陷阱内部打印 console.log(receiver === proxy); 它会打印出 true ,表示这里 receiver 的确是和代理对象相等的。

所以 receiver 的确是可以表示代理对象,但是这仅仅是 receiver 代表的一种情况而已。

来看另外一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const parent = {
get value() {
return '19Qingfeng';
},
};

const proxy = new Proxy(parent, {
// get陷阱中target表示原对象 key表示访问的属性名
get(target, key, receiver) {
console.log(receiver === proxy); // log: false
console.log(receiver === obj) // log:true
return target[key];
},
});

const obj = {
name: 'wang.haoyu',
};

// 设置obj继承与parent的代理对象proxy
Object.setPrototypeOf(obj, proxy);


obj.value

上述的代码同样我在 proxy 对象的 get 陷阱上打印了 console.log(receiver === proxy); 结果却是 false 。

那么你可以稍微思考下这里的 receiver 究竟是什么呢?其实这也是 proxy 中 get 陷阱第三个 receiver 存在的意义。

它是为了传递正确的调用者指向

其实简单来说,get 陷阱中的 receiver 存在的意义就是为了正确的在陷阱中传递上下文。

涉及到属性访问时,不要忘记 get 陷阱还会触发对应的属性访问器,也就是所谓的 get 访问器方法。

我们可以清楚的看到上述的 receiver 代表的是继承与 Proxy 的对象,也就是 obj。

看到这里,我们明白了 Proxy 中 get 陷阱的 receiver 不仅仅代表的是 Proxy 代理对象本身,同时也许他会代表继承 Proxy 的那个对象。

其实本质上来说它还是为了确保陷阱函数中调用者的正确的上下文访问,比如这里的 receiver 指向的是 obj 。

当然,你不要将 revceiver 和 get 陷阱中的 this 弄混了,陷阱中的 this 关键字表示的是代理的 handler 对象。

Reflect中的Receiver

我们知道在 Proxy 中(以下我们都以 get 陷阱为例)第三个参数 receiver 代表的是代理对象本身或者继承与代理对象的对象,它表示触发陷阱时正确的上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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: 19Qingfeng
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 就大显神通了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 对象。

针对于 get 陷阱(当然 set 其他之类涉及到 receiver 的陷阱同理):

  • Proxy 中接受的 Receiver 形参表示代理对象本身或者继承与代理对象的对象。
  • Reflect 中传递的 Receiver 实参表示修改执行原始操作时的 this 指向。
作者

胡兆磊

发布于

2022-07-25

更新于

2022-10-23

许可协议