Skip to content

Instantly share code, notes, and snippets.

@createvibe
Last active December 11, 2020 07:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save createvibe/f6806d5df97d848170bbe6486db24726 to your computer and use it in GitHub Desktop.
Save createvibe/f6806d5df97d848170bbe6486db24726 to your computer and use it in GitHub Desktop.
Deep Observable Proxy - Maintains the observed object path for complex data types, so developers can know which root properties have been modified.
/**
* The ProxyObserver recursively wraps complex objects for observations
* @param {{}|[]} target The target you want to observer
* @param {function} observer
* @throws TypeError if observer is not a function
*/
function ProxyObserver(target, observer) {
if (typeof observer !== 'function') {
throw new TypeError('Expecting observer to be a callable function.');
}
return new Proxy(target, {
get: (target, prop, receiver) => {
const value = target[prop];
if (typeof value === 'object') {
return ProxyObserver(value, observer.bind(null, {
target: target,
prop: prop,
value: value,
oldValue: value,
receiver: receiver
}));
}
return value;
},
set: (target, prop, value, receiver) => {
const oldValue = Reflect.get(target, prop, receiver);
if (oldValue === value) {
return true;
}
observer({target, prop, value, oldValue, receiver});
return Reflect.set(target, prop, value, receiver);
}
});
}
import ProxyObserver from './ProxyObserver';
const data = {
one: 'foo',
two: [1,'two',3,'four',5,[6,7]],
three: {
foo: 'test',
bar: 'test'
}
};
const proxy = ProxyObserver(data, function() {
console.log('we got a changed value!');
/*
the arguments to the function are grouped objects from
each level in the object path.
we refer to these grouped objects as the observable chain.
each link in the chain represents a different observed
level in the object path.
object.child.data = 'value';
object is level 1
child is level 2
data is level 3
this observable function is called only once per change,
at the last level, level 3. the arguments contain an
object for each level in the observed path.
*/
let name, value;
const path = [];
const chain = Array.prototype.slice.call(arguments);
while (chain.length !== 0) {
const link = chain.shift();
path.push( link.prop );
if (!name) {
name = link.prop;
}
value = link.value;
}
console.log('>', name, '<', path.join('.'), '=', value);
// name holds the root property in the proxy that was modified
// value holds the new value for the last property in the chain
});
proxy.two[5][1] = 'XXX';
// console: we got a changed value!
// console: > two < two.5.1 = XXX
proxy.three.foo = 'testing nested object';
// console: we got a changed value!
// console: > three < three.foo = testing nested object
proxy.three.bar = {
test: 'new object',
list: [1,2,3]
};
// console: we got a changed value!
// console: > three < three.bar = {test: "new object", list: Array(3)}
proxy.three.bar.list.push(4);
// console: we got a changed value!
// console: > three < three.bar.list.3 = 4
proxy.three.bar.list.splice(2,0,9);
// console: we got a changed value!
// console: > three < three.bar.list.4 = 4
// console: we got a changed value!
// console: > three < three.bar.list.3 = 3
// console: we got a changed value!
// console: > three < three.bar.list.2 = 9
console.log(proxy.three.bar.list.slice());
// console: (5) [1, 2, 9, 3, 4]
console.log(data.three.bar.list);
// console: (5) [1, 2, 9, 3, 4]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment