3

I'm trying to find similar functionality to AngularJS's $watch function (as defined here) which allows for 'watching of complex objects' and their changes. To the best of my knowledge, I understand it as being able to watch changes to variables within the object even if they themselves are also within an object (within the object being watched).

I wish to have this same 'watchability' in native JavaScript (or JQuery) but I can't seem to find anything. I know of Object.watch() and the Polyfill as found here but I'm pretty certain this only does reference checking or only watches the 'immediate' variables within the object and not anything that is nested so to speak and does not check properties 'deep' inside the object.

Does anyone know of any library, functions, anything that could help me to provide this 'deep watching' capability? Or even help me to understand Object.watch() a bit better if it does in-fact provide what I'm wanting?

I am creating a real-time music application and want to have a 'deep' watch on the instrument so I can see if any of its variables, parameters, etc.. change so I can sync it to the server and other clients.

Any help would be greatly appreciated, thanks!

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Alistair Hughes
  • 387
  • 2
  • 3
  • 13
  • 1
    **Note:** linked questions are either deprecated solutions or solutions that are old. Solution with proxy is more universal and modern. Proxy object have [92% support](https://caniuse.com/#feat=proxy) (don't work in IE though - check [ES6 Proxy Polyfill for IE11](https://stackoverflow.com/q/45285992/387194)) – jcubic Oct 05 '19 at 09:02

3 Answers3

11

As @Booster2ooo mention you can use Proxy object to observe the changes, you can use something like this:

function proxify(object, change) {
    // we use unique field to determine if object is proxy
    // we can't test this otherwise because typeof and
    // instanceof is used on original object
    if (object && object.__proxy__) {
         return object;
    }
    var proxy = new Proxy(object, {
        get: function(object, name) {
            if (name == '__proxy__') {
                return true;
            }
            return object[name];
        },
        set: function(object, name, value) {
            var old = object[name];
            if (value && typeof value == 'object') {
                // new object need to be proxified as well
                value = proxify(value, change);
            }
            object[name] = value;
            change(object, name, old, value);
        }
    });
    for (var prop in object) {
        if (object.hasOwnProperty(prop) && object[prop] &&
            typeof object[prop] == 'object') {
            // proxify all child objects
            object[prop] = proxify(object[prop], change);
        }
    }
    return proxy;
}

and you can use this fuction like this:

object = proxify(object, function(object, property, oldValue, newValue) {
    console.log('property ' + property + ' changed from ' + oldValue +
                ' to ' + newValue);
});

...

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • Thank you very much for giving an example of a proxy function as I was confused to how it applied to my problem. The function you gave does show literally all the changes to my object which is great! I now need to adjust it to show what I need :) As I'm using your code, I was thinking to mark your as the answer but you mentioned it is an extension of the other comment, what do you think I should mark as the answer? – Alistair Hughes Mar 12 '17 at 14:08
  • @AlistairHughes not sure, maybe it's better because it have actual code so it will be useful for other people that will have the same issue. – jcubic Apr 06 '17 at 08:47
  • @jcubic thanks for this great example of using Proxy – Zig Shanklin Apr 07 '21 at 05:28
2

You shouldn't use Object.watch

Warning: Generally you should avoid using watch() and unwatch() when possible. These two methods are implemented only in Gecko, and they're intended primarily for debugging use. In addition, using watchpoints has a serious negative impact on performance, which is especially true when used on global objects, such as window. You can usually use setters and getters or proxies instead. See Browser compatibility for details. Also, do not confuse Object.watch with Object.observe.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch

I'd rather have a look at Proxies:

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

And maybe MutationObserver if the DOM in implied:

MutationObserver provides developers with a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Explore & enjoy :)

Booster2ooo
  • 1,373
  • 9
  • 11
1

jcubic's answer is solid, but unfortunately it will not work with nested objects.

I published a library on GitHub (Observable Slim) that will allow you to observe/watch for changes that occur to a object and any nested children of that object. It also has a few extra features:

  • Reports back to a specified callback whenever changes occur.
  • Will prevent user from trying to Proxy a Proxy.
  • Keeps a store of which objects have been proxied and will re-use existing proxies instead of creating new ones (very significant performance implications).
  • Written in ES5 and employs a forked version of the Proxy polyfill so it can be deployed in older browsers fairly easily and support Array mutation methods.

It works like this:

var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
    console.log(JSON.stringify(changes));
});

p.testing.blah = 42; // console:  [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]

Please feel free to take a look and hopefully contribute as well!

Elliot B.
  • 17,060
  • 10
  • 80
  • 101
  • I'm not sure if you're correct, my proxify function is recursive it should work for any nested object. Each nested object should also be proxified. – jcubic Jan 27 '20 at 09:35