I'm creating an IIFE like below. It returns getters and setters to an array variable stored internally. I wish to intercept changes made to that array - the console.log
is intended to indicate that in the setter below.
const a = (function() {
let arr = [];
return {
get arr() {return arr},
set arr(v) {
console.log("new arr", v);
arr = v;
},
}
})();
This works fine if I completely reassign arr
, e.g. a.arr = [1, 2]
.
But it doesn't intercept changes made to contents of the array, e.g. a.arr.push(3)
or a.arr.shift()
.
Looking for any ideas on how to intercept these content changes.
This is an alternate attempt using the new Proxy object:
a = (function() {
let details = {
arr: []
}
function onChangeProxy(object, onChange) {
const handler = {
apply: function (target, thisArg, argumentsList) {
onChange(thisArg, argumentsList);
return thisArg[target].apply(this, argumentsList);
},
defineProperty: function (target, property, descriptor) {
Reflect.defineProperty(target, property, descriptor);
onChange(property, descriptor);
return true;
},
deleteProperty: function(target, property) {
Reflect.deleteProperty(target, property);
onChange(property, descriptor);
return;
}
};
return new Proxy(object, handler);
};
return onChangeProxy(details, (p, d) => console.log(p, d));
})();
The problem remains the same. Still unable to observe changes made to the contents of a.arr
using anything from a.arr[0] = 1
to a.push(3)
.
The solution is based on this commit by Kris on Sindre's 'on-change' library.
Explanation of the solution, by Kris:
In the
set
trap, my goal is to determine if the value provided is aProxy
produced by a previous call to theget
trap. If it is such aProxy
, any property access will be intercepted by our ownget
trap. So, when I accessvalue[proxyTarget]
ourget
trap will be invoked, which is coded to returntarget
whenproperty === proxyTarget
(line 46). If the value passed toset
is not an on-change-created Proxy, thenvalue[proxyTarget]
isundefined
.
Full code of the solution:
(object, onChange) => {
let inApply = false;
let changed = false;
function handleChange() {
if (!inApply) {
onChange();
} else if (!changed) {
changed = true;
}
}
const handler = {
get(target, property, receiver) {
const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
const value = Reflect.get(target, property, receiver);
// Preserve invariants
if (descriptor && !descriptor.configurable) {
if (descriptor.set && !descriptor.get) {
return undefined;
}
if (descriptor.writable === false) {
return value;
}
}
try {
return new Proxy(value, handler);
} catch (_) {
return value;
}
},
set(target, property, value) {
const result = Reflect.set(target, property, value);
handleChange();
return result;
},
defineProperty(target, property, descriptor) {
const result = Reflect.defineProperty(target, property, descriptor);
handleChange();
return result;
},
deleteProperty(target, property) {
const result = Reflect.deleteProperty(target, property);
handleChange();
return result;
},
apply(target, thisArg, argumentsList) {
if (!inApply) {
inApply = true;
const result = Reflect.apply(target, thisArg, argumentsList);
if (changed) {
onChange();
}
inApply = false;
changed = false;
return result;
}
return Reflect.apply(target, thisArg, argumentsList);
}
};
return new Proxy(object, handler);
};
This has solved my problem, without resorting to the hack of checking for array modifying methods.
I've sorted this, for now, with help from David Walsh's post here. It's still ugly, but works for now.
Updated the onChanged
Proxy maker with a recursive-ish get
trap.
get: function (target, property, receiver) {
let retval;
try {
retval = new Proxy(target[property], handler);
} catch (err) {
retval = Reflect.get(target, property, receiver);
}
if (mutators.includes(property))
onChange(target, property, receiver);
return retval;
},
Also added a list of functions to check the get trap against (the ugly, hacky bit):
const mutators = [
"push",
"pop",
"shift",
"unshift",
"splice",
"reverse",
"fill",
"sort"
]
This seems to be working in my testing so far.
Thanks for pointing in the correct direction.