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
修改getter
setter
内部指针(但是不是所有的地方都需要配置Reflect
,Vue3 响应数据根据情况使用Reflect
)Reflect
从 规范 28.15 来分析,如果target
对象中指定了getter
,receiver
则为getter
调用时的this
值
五、参考
上面如果分析不妥,还请大佬指教,谢谢 😊😊😊
如果对您有帮助,欢迎来个👍🏻
转载自:https://juejin.cn/post/7100575242287841310