Reputation: 1618
I have a function that takes an object and returns a proxied version of this object, which can be mutated without affecting the original object.
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);
};
Example
//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?
Question
So, from my point of view, expressions
proxy8.x.y = proxy8.x.y + 2;
//doesn't work
and
const t = proxy8.x.y + 2;
proxy8.x.y = t;
//works
are almost identical.
But why one of them doesn't work? Why does an expression that contains a deeply nested proxy object property not update the same proxy object property?
Upvotes: 0
Views: 47
Reputation: 665276
The difference - and the problem - is the order of operations:
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);
In the two statements
const t = obj.x.y + 2;
obj.x.y = t;
the access of .y
is executed first, replacing obj.x
with a new object in that step, followed by the assignment to the target .y
that again replaces obj.x
with a new object (on which the new y
is then stored).
In contrast, in the single statement
obj.x.y = obj.x.y + 2;
the target reference is evaluated first, replacing obj.x
in that step, followed by the access of y
that again replaces obj.x
with a new value, and in the end the new y
is stored in the object resulting from the first step that is no longer the current value of obj.x
.
To fix this, don't replace your proxy properties with new objects on every single access, but a) only if necessary when an assignment happens or b) only once per target.
Upvotes: 0