Why has Object.observe() been deprecated

Javascript

Javascript Problem Overview


Is there an alternative approach?

Is there another way to do change detection in object?

There is the Proxy method, but can anyone tell me how can I achieve this using Proxy:

var obj = {
  foo: 0,
  bar: 1
};

Object.observe(obj, function(changes) {
  console.log(changes);
});

obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]

obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]

Javascript Solutions


Solution 1 - Javascript

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'}

Solution 2 - Javascript

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.

Solution 3 - Javascript

@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.

Solution 4 - Javascript

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShadView Question on Stackoverflow
Solution 1 - JavascriptDan PrinceView Answer on Stackoverflow
Solution 2 - JavascriptGullerYAView Answer on Stackoverflow
Solution 3 - JavascriptNiRUSView Answer on Stackoverflow
Solution 4 - JavascriptNVRMView Answer on Stackoverflow