49

It is relatively trivial to watch for changes in an array in Javascript.

One method I use is like this:

// subscribe to add, update, delete, and splice changes
Array.observe(viewHelpFiles, function(changes) {
  // handle changes... in this case, we'll just log them 
  changes.forEach(function(change) {
    console.log(Object.keys(change).reduce(function(p, c) {
      if (c !== "object" && c in change) {
        p.push(c + ": " + JSON.stringify(change[c]));
      }
      return p;
    }, []).join(", "));
  });
});

However, I have recently read that Array.observe is deprecated and we should use the proxy object instead.

How can we detect changes in an array the Proxy object? I am unable to find any examples, anyone interested in elaborating?

Igor Raush
  • 15,080
  • 1
  • 34
  • 55
w2olves
  • 2,229
  • 10
  • 33
  • 60
  • I looked for "proxy observe" in Google, got this https://gist.github.com/ebidel/1b553d571f924da2da06 – elclanrs Feb 24 '16 at 18:34
  • 1
    Note that proxies is currently only supported in Firefox, and a few transpilers, it's not something you'd use in production code. – adeneo Feb 24 '16 at 18:36
  • 3
    Proxy is now supported in all major browsers, except for Internet Explorer. – Björn Nov 11 '16 at 08:48

2 Answers2

60

From what I can read from the MDN page, you can create a general handler where you can handle all the changes to any object.

In a sense, you write an interceptor, that will intervene each time you get a value from the array or set a value. You can then write your own logic to follow the changes.

var arrayChangeHandler = {
  get: function(target, property) {
    console.log('getting ' + property + ' for ' + target);
    // property is index in this case
    return target[property];
  },
  set: function(target, property, value, receiver) {
    console.log('setting ' + property + ' for ' + target + ' with value ' + value);
    target[property] = value;
    // you have to return true to accept the changes
    return true;
  }
};

var originalArray = [];
var proxyToArray = new Proxy( originalArray, arrayChangeHandler );

proxyToArray.push('Test');
console.log(proxyToArray[0]);

// pushing to the original array won't go through the proxy methods
originalArray.push('test2');

// the will however contain the same data, 
// as the items get added to the referenced array
console.log('Both proxy and original array have the same content? ' 
  + (proxyToArray.join(',') === originalArray.join(',')));

// expect false here, as strict equality is incorrect
console.log('They strict equal to eachother? ' + (proxyToArray === originalArray));

Which then outputs:

getting push for 
getting length for 
setting 0 for  with value Test 
setting length for Test with value 1
getting 0 for Test
Test

The caveat for the proxy, is that everything which is defined on an object, will be intercepted, which can be observed when using the push method.

The original object that will be proxied doesn't mutate, and changes done to the original object will not be caught by the proxy.

Icepickle
  • 12,689
  • 3
  • 34
  • 48
  • 2
    A caveat, not supported in IE11. – Chexpir May 02 '16 at 14:02
  • 3
    @Chexpir true, according to MDN it's even totally not supported by IE, however it should be supported by Edge, which is the current MS browser – Icepickle May 02 '16 at 14:08
  • 5
    But you are not observing the array at all, you create a proxy and the proxy forwards changes to the array. The question is how to actually observe the array itself. Naming the proxy "arrayToObserve" is misleading. Those proxy and the array are two entirely different objects. – Winchestro Aug 03 '17 at 13:37
  • 1
    @Winchestro that's true, you are not observing the object at all, but the proxy is doing that for you, however if you would give the `arrayToObserve` out, remarking that an array object is being returned by any of your api functions, then does that matter? The end user would know he can interact with the array as he is expecting it to be. Is this just a naming question? (though I must admit, looking back at the post, I think my language could do with some minor updates) – Icepickle Aug 03 '17 at 13:40
  • @Icepickle It matters if one does not want to replace everything with proxies just to observe. Say you have millions of objects and want to "observe" 10 of them at a time. Are you replacing all millions of them with proxies just to be able to observe any 10 of them? – Winchestro Aug 03 '17 at 14:37
  • @Winchestro No, ideally not no ;) However, that point is going completely next to the question at hand, though I do follow you that my naming could/should be better, and I will check to give the post an edit in the evening, with a warning that the original object doesn't get mutated, and that the variable is now a proxy, and not an arrayToObserve ;-) – Icepickle Aug 03 '17 at 15:11
  • @Winchestro I updated the post with some more explanations, I hope it suits your needs a bit better now ;) – Icepickle Aug 03 '17 at 21:48
  • @Icepickle It does. Thank you :) – Winchestro Aug 04 '17 at 01:55
  • it works. but those efforts will be removed after `concat` and/or `map` so it is not so ideal. – zipper Mar 27 '19 at 16:52
  • well, that result can be anew a proxy with the same changehandler, or is something else you mean @zipper ? – Icepickle Mar 27 '19 at 20:31
16

You can do somthing like this

new Proxy([], {
    get(target, prop) {
        const val = target[prop];
        if (typeof val === 'function') {
            if (['push', 'unshift'].includes(prop)) {
                return function (el) {
                    console.log('this is a array modification');
                    return Array.prototype[prop].apply(target, arguments);
                }
            }
            if (['pop'].includes(prop)) {
                return function () {
                    const el = Array.prototype[prop].apply(target, arguments);
                    console.log('this is a array modification');
                    return el;
                }
            }
            return val.bind(target);
        }
        return val;
    }
});
coderek
  • 1,860
  • 14
  • 20