Proxy为什么需要Reflect,从原理层面理解它们
一、开篇
最近在研究Vue3的响应原理,发现Vue3响应式数据是基于Proxy实现的,为了更好学习响应数据原理,需要对Proxy深入理解。在某些时刻,Proxy还需要Reflect辅助才能五杀,那么下来我们对它们进行深入解刨,研究研究
二、Proxy
先看一下,Proxy的基本用法
const obj = { name: "World" };
const handler = {
get(target, key) {
return `Hello ${target[key]}`;
},
};
const p = new Proxy(obj, handler);
console.log(p.name); // Hello World
在执行p.name的时候,Proxy做了些什么
不知道大家有木有和我有同样的疑问❓,在执行p.name的时候,Proxy做了些什么,直接调用handler对象内的方法get。那咱们看看 ECMAScript@2023 10.5 里面介绍了Proxy Handler Methods
| Internal Method | Handler Method |
|---|---|
| [[GetPrototypeOf]] | getPrototypeOf |
| [[SetPrototypeOf]] | setPrototypeOf |
| [[IsExtensible]] | isExtensible |
| [[PreventExtensions]] | preventExtensions |
| [[GetOwnProperty]] | getOwnPropertyDescriptor |
| [[DefineOwnProperty]] | defineProperty |
| [[HasProperty]] | has |
| [[Get]] | get |
| [[Set]] | set |
| [[Delete]] | deleteProperty |
| [[OwnPropertyKeys]] | ownKeys |
| [[Call]] | apply |
| [[Construct]] | construct |
当执行 p.name的时候, 相当于 执行了 Proxy 内置放法 [[Get]] 然后再执行 Handler Method,那它是怎么调用 get 呢,咱们继续 ECMAScript@2023 10.5.8 [[GET]]的介绍
核心步骤是1-7、10
- 让
handler成为O.[[ProxyHandler]]--- O是代理对象 - 如果
handler是null,抛出语法异常 - 断言
handler类型是Object - 让
target成为O.[[ProxyTarget]] - 让
trap成为GetMethod(handler, "get")--- (查阅GetMethod,从handler内获取get属性) - 如果
trap是undefined,返回target.[[Get]](P, Receiver)7、10. 否则 返回 Call(trap,handler, «target,P,Receiver»)
细心的小伙伴,从上面看出,如果 handler 是 undefined,就抛出异常
如果 handler 中没有 get方法,从代理对象中[[GET]]获取值并返回,即target.[[Get]](P, Receiver)
那么直接返回 handler 中的get方法执行的值,get.call(handler,target, P, Receiver)(P是key--p.name的name)
以上就是p.name 获取值的整个流程
二、Receiver 是什么
第6、7步中 Receiver 是什么呢,我们可以从下面的案例1中看看 到底是啥
const obj = {
name: "obj",
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
console.log(receiver === proxy); // true
return target[key];
},
};
const proxy = new Proxy(obj, handler);
console.log(proxy.value); // obj
上面的案例,receiver 就是代理对象
那么再看看下面的案例2
const obj = {
name: "obj",
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
console.log(receiver === proxy); // false
console.log(receiver === child); // true
console.log(target === obj); // true
return target[key];
},
};
const proxy = new Proxy(obj, handler);
const child = {
name: "child",
};
Object.setPrototypeOf(child, proxy);
console.log(child.value); // obj
将 child 继承 proxy 之后,child.value 输出的是 obj,但是这个不符合我们想要的,应该是输出值是 child 的。那我们就来分析一下
child.value,由于child没有value属性,因此需要从父级获取value- 父级是
proxy,child.value就相当于是proxy.value,因此就和上面的例子一样,输出obj.value为obj那我们怎么才能child.value输出child呢? 细心的小伙伴,可能发现这里的receiver值发生了变更,案例1 中是proxy,而 案例2中是child。
可以得出的结论,
receiver就是Proxy或者继承Proxy的对象,proxy.value,proxy是Proxy,receiver就是proxy。child.value,child是继承Proxy的对象,因此receiver就是child。
这里需要用到 Reflect 结合 receiver 来实现我们想要的结果了(child.value 输出 child)。
修改一下get,引入Reflect来实现 child.value 输出 child
const obj = {
name: "obj",
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
};
const proxy = new Proxy(obj, handler);
const child = {
name: "child",
};
Object.setPrototypeOf(child, proxy);
console.log(child.value); // child
三、Reflect 工作原理
那 Reflect 怎么实现能实现上面的功能呢,即 child.value 输出 child。
1. 从 MDN 来看 Reflect
先看看MDN是怎么介绍 Reflect.get(target, propertyKey[, receiver]) 方法的:
Reflect.get()方法与从 对象 (target[propertyKey]) 中读取属性类似,但它是通过一个函数执行来操作的。
Reflect.get()就相当于 target[propertyKey] 从target读取值
另外还有一段 receiver解释
如果
target对象中指定了getter,receiver则为getter调用时的this值。
上面的这句话就极为重要了,原来 receiver是来执行 target 对象内的 getter调用时的this指向的。那一切就真相大白了。
案例3 部分代码
const obj = {
name: "obj",
get value() {
return this.name;
},
};
const handler = {
get(target, key, receiver) {
console.log(receiver === child); // true
return Reflect.get(target, key, receiver);
},
}
案例3 中 receiver 指向 child, 通过 Reflect.get(target, key, receiver) 修改 obj 中getter的this指向为 child,因此 getter 返回的 this.name 就是 child.name,因此 child.value 输出 child。
2. 从 ECMAScript规范中分析Reflect.get

- 如果
target不是Object,就会抛出语法错误 - 让key 成为 ?
ToPropertyKey(propertyKey)。 - 如果
receiver不存在,将receiver赋值为target - 返回 ?
target.[Get]。
Reflect.get(target, key, receiver) 这里直接写成Reflect.get(obj, value, child)
那么 Reflect.get(obj, value, child) 等价与 obj.[Get]。接下来分析 obj.[[Get]]

- 返回 ? OrdinaryGet
那么
Reflect.get(obj, value, child)等价与 OrdinaryGet。这里的O是obj,P是value,child是Receiver。 接下来分析OrdinaryGet
- 让
desc成为O.[GetOwnProperty] - 如果
desc是undefined- 让
parent成为O.[GetPrototypeOf]. - 如果
parent是null,返回 undefined - 否则返回
parent.[Get].
- 让
[IsDataDescriptor](desc)为 true,直接把desc对应的值返回- 断言
desc拥有get - 让
getter成为desc.[[Get]]. - 如果
getter是undefined, 返回 undefined - 否则 返回
Call(getter, Receiver),不在分析 Call原理了,这里就相当于getter.call(Receiver),有兴趣的小伙伴去看一下 ECMAScript® 2023 Language
经过上面的一番分析,原来 Reflect.get(obj,value,child), 就相当于 执行 obj 里面的 getter,修改了 getter内部的指针 get.call(child)
四、总结
Proxy功 规范 10.5.8 了解到 [[GET]] 调用内部get捕捉器Receiver是:Proxy或者继承Proxy的对象Proxy代理的对象,需要配合Reflect修改gettersetter内部指针(但是不是所有的地方都需要配置Reflect,Vue3 响应数据根据情况使用Reflect)Reflect从 规范 28.15 来分析,如果target对象中指定了getter,receiver则为getter调用时的this值
五、参考
上面如果分析不妥,还请大佬指教,谢谢 😊😊😊
如果对您有帮助,欢迎来个👍🏻
转载自:https://juejin.cn/post/7100575242287841310