我有一个函数,它接受一个对象并返回该对象的代理版本,可以在不影响原始对象的情况下对其进行修改。
const createProxy4 = (obj) => {
const handler = {
get(target, prop) {
//if the value of a property is an object - replace it with another proxy before return
if (target[prop] !== null && typeof target[prop] === "object") {
target[prop] = new Proxy({ ...target[prop] }, handler);
}
//if the value is a primitive - just return it
return target[prop];
},
set(target, prop, value) {
//to update the property - just assign a new value to it
target[prop] = value;
return true;
},
};
//create a shallow copy of the original object
return new Proxy({ ...obj }, handler);
};
示例
//here is an original object
const obj8 = {
x: { y: 5 },
};
//let's create a proxied copy of it
const proxy8 = createProxy4(obj8);
//let's change the property of the proxied object
proxy8.x.y = 27;
console.log(obj8); //{ x: { y: 5 } } - original object is not affected
console.log(proxy8); //{ x: { y: 27 } } - proxied object property is changed as expected
//let's change proxy using expression which uses it's own property
proxy8.x.y = proxy8.x.y + 2;
console.log(obj8); //{ x: { y: 5 } } - original object is not affected
console.log(proxy8); //{ x: { y: 27 } } - NOTHING CHANGED! WHY?
//let's refactor the previous expression using an intermediate variable "t"
const t = proxy8.x.y + 2;
proxy8.x.y = t;
console.log(obj8); //{ x: { y: 5 } } - original object is not affected
console.log(proxy8); //{ x: { y: 29 } } - Changes applied as expected. WHY?
问题
所以,从我的角度来看,表达式
proxy8.x.y = proxy8.x.y + 2;
//doesn't work
和
const t = proxy8.x.y + 2;
proxy8.x.y = t;
//works
几乎相同。
但为什么其中之一不起作用呢?为什么包含深度嵌套代理对象属性的表达式不更新相同的代理对象属性?
差异 - 和问题 - 是操作顺序:
function createProxy(obj) {
let id = 0;
const handler = {
get(target, prop, receiver) {
let value = Reflect.get(target, prop, receiver);
if (value !== null && typeof value === "object") {
console.log(`Replacing ${prop} on ${target.id} with new object ${++id}`);
value = target[prop] = new Proxy({ ...value, id }, handler);
}
return value;
},
set(target, prop, value, receiver) {
console.log(`Assigning ${prop} on object ${target.id}`);
return Reflect.set(target, prop, value, receiver);
},
};
return new Proxy({ ...obj, id: 'root' }, handler);
};
const obj = createProxy({
x: { y: 5 },
});
console.log('Single statement');
obj.x.y = obj.x.y + 2;
console.log('Done:');
console.log(obj);
console.log('Two statements');
const t = obj.x.y + 2;
obj.x.y = t;
console.log('Done:');
console.log(obj);
console.log('Compound assignment');
obj.x.y += 2;
console.log('Done:');
console.log(obj);
在两个声明中
const t = obj.x.y + 2;
obj.x.y = t;
首先执行对
.y
的访问,在该步骤中将 obj.x
替换为新对象,然后对目标 .y
进行赋值,再次将 obj.x
替换为新对象(在该目标上新的 y
然后被存储)。
相比之下,在单一声明中
obj.x.y = obj.x.y + 2;
首先评估目标表达式,在该步骤中替换
obj.x
,然后访问 y
,再次用新值替换 obj.x
,最后将新的 y
存储在结果对象中从第一步开始不再是 obj.x
的当前值。
要解决此问题,不要在每次访问时都将代理属性替换为新对象,而是 a) 仅在发生分配时必要时替换,或 b) 每个目标仅替换一次。