455 字
2 分钟
JS代理与原型链的陷阱
var o1,o2,o3,o4
o1 = { coin: 11 };
let handler = { get(target, prop, receiver) { console.log(`Getting ${prop}!`); return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.info(target === o1, receiver === o4) console.log(`Setting ${prop} to ${value}!`); return target[prop]=value; }}
o2 = new Proxy(o1, handler);o3 = Object.create(o2);o4 = Object.create(o3);
o4.coin = 44;
console.info(o1.coin, o2.coin, o3.coin, o4.coin);看上去只是一个很简单的 o4.coin = 44 ,但是却会涉及到原型链的修改
原型链是这样:
o4 --> o3 --> o2(Proxy) --> o1handler中的代码:
set(target, prop, value, receiver) { console.info(target === o1, receiver === o4) console.log(`Setting ${prop} to ${value}!`); return target[prop] = value;}这里的target是指向o1, receiver则是指向o4
- o1.coin = 44 ✅,因为 set handler 实际上修改的是 o1;
- o2.coin = 44 ✅,Proxy 读取的是 o1;
- o3.coin = 44 ✅,通过原型链 o3 → o2(Proxy) → o1;
- o4.coin = 44 ❌,因为 o4 自身并没有 coin 属性,仍然会沿原型链读取最终到 o1,值是 44。
最终打印结果为:
true trueSetting coin to 44!Getting coin!Getting coin!Getting coin!44 44 44 44target vs receiver?
- target 是你传给 Proxy 构造函数的原始对象(例如 o1);
- receiver 是实际访问/调用的对象(比如赋值时是 o4);
- receiver 是 希望在哪个对象上定义属性 的“调用者”,特别关键在继承场景中。
因此在 handler中,应该使用Reflect来做,而不是手动赋值
set(target, prop, value, receiver) { return Reflect.set(target, prop, value, receiver);}测试getter和setter
var o1, o2, o3, o4;
o1 = { coin: 11 };
// 在 o2 上定义 getter 和 setter,模拟 Proxy 的行为o2 = Object.create(o1);
Object.defineProperty(o2, 'coin', { get() { console.log(`Getting coin!`); return o1.coin; }, set(value) { console.info(o1 === o1, true); // 模拟 Proxy 中 target === o1, receiver === o4 console.log(`Setting coin to ${value}!`); o1.coin = value; }, configurable: true, enumerable: true,});
o3 = Object.create(o2);o4 = Object.create(o3);
// 测试写入o4.coin = 44;
// 输出每个对象的 coin 值console.info(o1.coin, o2.coin, o3.coin, o4.coin);如上,会遇到同样的问题,此时输出是:
true trueSetting coin to 44!Getting coin!Getting coin!Getting coin!44 44 44 44