Why has Object.observe() been deprecated
You can achieve this with getters and setters.
var obj = {
get foo() {
console.log({ name: 'foo', object: obj, type: 'get' });
return obj._foo;
},
set bar(val) {
console.log({ name: 'bar', object: obj, type: 'set', oldValue: obj._bar });
return obj._bar = val;
}
};
obj.bar = 2;
// {name: 'bar', object: <obj>, type: 'set', oldValue: undefined}
obj.foo;
// {name: 'foo', object: <obj>, type: 'get'}
Alternatively, in a browser with support for Proxies, you can write a more generic solution.
var obj = {
foo: 1,
bar: 2
};
var proxied = new Proxy(obj, {
get: function(target, prop) {
console.log({ type: 'get', target, prop });
return Reflect.get(target, prop);
},
set: function(target, prop, value) {
console.log({ type: 'set', target, prop, value });
return Reflect.set(target, prop, value);
}
});
proxied.bar = 2;
// {type: 'set', target: <obj>, prop: 'bar', value: 2}
proxied.foo;
// {type: 'get', target: <obj>, prop: 'bar'}
Disclaimer: I'm the author of the object-observer library suggested below.
I'd not go with getters/setters solution - it's complicated, not scalable and not maintainable. Backbone did their two-way binding that way and the boilerplate to get it working correctly was quite a piece of a code.
Proxies is the best way to achieve what you need, just add to the examples above some callbacks registration and management and execute them upon a changes.
As regarding to the polyfill libraries: some/most of these implemented utilizing 'dirty check' or polling technique - not efficient, not performant. Occasionally, this is the case of the polyfill pointed out by Nirus above.
I'd recommend to pick up some library that does observation via Proxies. There are a few out there, object-observer being one of them: written for this use-case exactly, utilizes native Proxies, provides deep-tree observation etc.
@Dan Prince solution should be the first choice always.
Just in case for some reason if you want to support browsers that are quite older, i would suggest you to go for any polyfill libraries available on Github or use Object.defineProperties API which is supported in IE 9 to emulate the same.
var obj = Object.defineProperties({}, {
"foo":{
get:function(){
console.log("Get:"+this.value);
},
set:function(val){
console.log("Set:"+val);
this.value = val;
}
},
"bar":{
get:function(){
console.log("Get:"+this.value);
},
set:function(val){
console.log("Set:"+val);
this.value = val;
}
}
});
Note: This is not a scalable solution. Make an educated decision whether to use the above API for larger data objects and computation intensive requirements.
Simplest observer, that watch only for changes on any Object.
Return the changed property and the new value, to match OP original query.
Notice it does require a double assignment, using this way.
/* Simplest Object Observer */
Object.observe = (o, f) => new Proxy(o, { set: (a, b, c) => f(a, b, c) })
var obj = {
foo: 0,
bar: 1
};
// Assignment after creation, keep the variable name, extend.
obj = Object.observe(obj, function(target, prop, changes) {
console.log("Change detected!", prop, changes)
})
obj.baz = 2;
obj.foo = 'hello';
I believe it can be used in a lot of way, for everything that need chain Reactive assignments.
Similarly, as a Object prototype, it allows to chain the declaration in a single call. On both example, we can use this
in the observe call, as shown here.
Notice in this case, we have to wrap the Object into parenthesis first.
/* Simplest Prototype Object Observer */
Object.prototype.observe = (o, f) => new Proxy(o, { set: (a, b, c) => f(a, b, c) })
const obj = ({
foo: 0,
bar: 1
}).observe(this, ({}, prop, changes) => console.log("Change detected!", prop, changes))
obj.baz = 2;
obj.foo = 'hello';
To actually set the values on the observed Object:
/* Simplest Observer function */
const observe = (o, f) => new Proxy(o, { set: (a, b, c) => f(a, b, c) })
let obj = {
foo: 0,
bar: 1
};
obj = observe(obj, (target, prop, changes) => {
target[prop] = changes
console.log("Change detected!", prop, changes)
})
obj.foo = -10 // Hot start
setInterval(() => obj.foo = obj.foo + 1, 3000)