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 创建了一个基于 obj 对象的代理,同时在 Proxy 中声明了一个 get 陷阱。当访问我们访问 proxy.name 时实际触发了对应的 get 陷阱,它会执行 get 陷阱中的逻辑,同时会执行对应陷阱中的逻辑,最终返回对应的 target[key] 也就是所谓的 wang.haoyu .
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;上述的例子中,我们在 Proxy 实例对象的 get 陷阱上接收了 receiver 这个参数。同时,我们在陷阱内部打印 console.log(receiver === proxy); 它会打印出 true ,表示这里 receiver 的确是和代理对象相等的。所以 receiver 的确是可以表示代理对象,但是这仅仅是 receiver 代表的一种情况而已。接下来我们来看另外一个例子:
const parent = { get value() { return '19Qingfeng'; }, }; const proxy = new Proxy(parent, { // get陷阱中target表示原对象 key表示访问的属性名 get(target, key, receiver) { console.log(receiver === proxy); return target[key]; }, }); const obj = { name: 'wang.haoyu', }; // 设置obj继承与parent的代理对象proxy Object.setPrototypeOf(obj, proxy); // log: false obj.value关于原型上出现的 get/set 属性访问器的“屏蔽”效果,我在这篇文章[7]中进行了详细阐述。这里我就不展开讲解了。我们可以看到,上述的代码同样我在 proxy 对象的 get 陷阱上打印了 console.log(receiver === proxy); 结果却是 false 。那么你可以稍微思考下这里的 receiver 究竟是什么呢?其实这也是 proxy 中 get 陷阱第三个 receiver 存在的意义。
... 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]; }, }); ...其实简单来说,get 陷阱中的 receiver 存在的意义就是为了正确的在陷阱中传递上下文。涉及到属性访问时,不要忘记 get 陷阱还会触发对应的属性访问器,也就是所谓的 get 访问器方法。我们可以清楚的看到上述的 receiver 代表的是继承与 Proxy 的对象,也就是 obj。看到这里,我们明白了 Proxy 中 get 陷阱的 receiver 不仅仅代表的是 Proxy 代理对象本身,同时也许他会代表继承 Proxy 的那个对象。其实本质上来说它还是为了确保陷阱函数中调用者的正确的上下文访问,比如这里的 receiver 指向的是 obj 。当然,你不要将 revceiver 和 get 陷阱中的 this 弄混了,陷阱中的 this 关键字表示的是代理的 handler 对象。
const parent = { get value() { return '19Qingfeng'; }, }; const handler = { get(target, key, receiver) { console.log(this === handler); // log: true console.log(receiver === obj); // log: true return target[key]; }, }; const proxy = new Proxy(parent, handler); const obj = { name: 'wang.haoyu', }; // 设置obj继承与parent的代理对象proxy Object.setPrototypeOf(obj, proxy); // log: false obj.valueReflect 中的 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);上述代码原理其实非常简单: