How to monitor array changes?

Is there a notification method in Javascript when an array is modified using push, pop, shift or index-based? I want something that could trigger an event that I could handle.

I know about the watch () function in SpiderMonkey, but this only works when the whole variable is set to something else.

+83
javascript javascript-events dom-events
Feb 24 '11 at 4:32
source share
6 answers

There are several options...

1. Override the push method

When navigating a fast and dirty route, you can override the push() method for your array 1 :

 Object.defineProperty(myArray, "push", { configurable: false, enumerable: false, // hide from for...in writable: false, value: function () { for (var i = 0, n = this.length, l = arguments.length; i < l; i++, n++) { RaiseMyEvent(this, n, this[n] = arguments[i]); // assign/raise your event } return n; } }); 

1 Alternatively, you can override Array.prototype.push() ... though messing with Array.prototype usually not recommended. However, if you prefer to do this, simply replace myArray with Array.prototype .

Now, this is just one method, and there are many ways to change the contents of an array. We probably need something more comprehensive ...

2. Create your own observable array

Instead of overriding methods, you can create your own observable array. This particular implementation copies the array into a new object, similar to an array, and provides custom methods push() , pop() , shift() , unshift() , slice() and splice() , as well as specialized indexes (provided that the size an array is changed by only one of the above methods or by the length property).

 function ObservableArray(items) { var _self = this, _array = [], _handlers = { itemadded: [], itemremoved: [], itemset: [] }; function defineIndexProperty(index) { if (!(index in _self)) { Object.defineProperty(_self, index, { configurable: true, enumerable: true, get: function() { return _array[index]; }, set: function(v) { _array[index] = v; raiseEvent({ type: "itemset", index: index, item: v }); } }); } } function raiseEvent(event) { _handlers[event.type].forEach(function(h) { h.call(_self, event); }); } Object.defineProperty(_self, "addEventListener", { configurable: false, enumerable: false, writable: false, value: function(eventName, handler) { eventName = ("" + eventName).toLowerCase(); if (!(eventName in _handlers)) throw new Error("Invalid event name."); if (typeof handler !== "function") throw new Error("Invalid handler."); _handlers[eventName].push(handler); } }); Object.defineProperty(_self, "removeEventListener", { configurable: false, enumerable: false, writable: false, value: function(eventName, handler) { eventName = ("" + eventName).toLowerCase(); if (!(eventName in _handlers)) throw new Error("Invalid event name."); if (typeof handler !== "function") throw new Error("Invalid handler."); var h = _handlers[eventName]; var ln = h.length; while (--ln >= 0) { if (h[ln] === handler) { h.splice(ln, 1); } } } }); Object.defineProperty(_self, "push", { configurable: false, enumerable: false, writable: false, value: function() { var index; for (var i = 0, ln = arguments.length; i < ln; i++) { index = _array.length; _array.push(arguments[i]); defineIndexProperty(index); raiseEvent({ type: "itemadded", index: index, item: arguments[i] }); } return _array.length; } }); Object.defineProperty(_self, "pop", { configurable: false, enumerable: false, writable: false, value: function() { if (_array.length > -1) { var index = _array.length - 1, item = _array.pop(); delete _self[index]; raiseEvent({ type: "itemremoved", index: index, item: item }); return item; } } }); Object.defineProperty(_self, "unshift", { configurable: false, enumerable: false, writable: false, value: function() { for (var i = 0, ln = arguments.length; i < ln; i++) { _array.splice(i, 0, arguments[i]); defineIndexProperty(_array.length - 1); raiseEvent({ type: "itemadded", index: i, item: arguments[i] }); } for (; i < _array.length; i++) { raiseEvent({ type: "itemset", index: i, item: _array[i] }); } return _array.length; } }); Object.defineProperty(_self, "shift", { configurable: false, enumerable: false, writable: false, value: function() { if (_array.length > -1) { var item = _array.shift(); delete _self[_array.length]; raiseEvent({ type: "itemremoved", index: 0, item: item }); return item; } } }); Object.defineProperty(_self, "splice", { configurable: false, enumerable: false, writable: false, value: function(index, howMany /*, element1, element2, ... */ ) { var removed = [], item, pos; index = index == null ? 0 : index < 0 ? _array.length + index : index; howMany = howMany == null ? _array.length - index : howMany > 0 ? howMany : 0; while (howMany--) { item = _array.splice(index, 1)[0]; removed.push(item); delete _self[_array.length]; raiseEvent({ type: "itemremoved", index: index + removed.length - 1, item: item }); } for (var i = 2, ln = arguments.length; i < ln; i++) { _array.splice(index, 0, arguments[i]); defineIndexProperty(_array.length - 1); raiseEvent({ type: "itemadded", index: index, item: arguments[i] }); index++; } return removed; } }); Object.defineProperty(_self, "length", { configurable: false, enumerable: false, get: function() { return _array.length; }, set: function(value) { var n = Number(value); var length = _array.length; if (n % 1 === 0 && n >= 0) { if (n < length) { _self.splice(n); } else if (n > length) { _self.push.apply(_self, new Array(n - length)); } } else { throw new RangeError("Invalid array length"); } _array.length = n; return value; } }); Object.getOwnPropertyNames(Array.prototype).forEach(function(name) { if (!(name in _self)) { Object.defineProperty(_self, name, { configurable: false, enumerable: false, writable: false, value: Array.prototype[name] }); } }); if (items instanceof Array) { _self.push.apply(_self, items); } } (function testing() { var x = new ObservableArray(["a", "b", "c", "d"]); console.log("original array: %o", x.slice()); x.addEventListener("itemadded", function(e) { console.log("Added %o at index %d.", e.item, e.index); }); x.addEventListener("itemset", function(e) { console.log("Set index %d to %o.", e.index, e.item); }); x.addEventListener("itemremoved", function(e) { console.log("Removed %o at index %d.", e.item, e.index); }); console.log("popping and unshifting..."); x.unshift(x.pop()); console.log("updated array: %o", x.slice()); console.log("reversing array..."); console.log("updated array: %o", x.reverse().slice()); console.log("splicing..."); x.splice(1, 2, "x"); console.log("setting index 2..."); x[2] = "foo"; console.log("setting length to 10..."); x.length = 10; console.log("updated array: %o", x.slice()); console.log("setting length to 2..."); x.length = 2; console.log("extracting first element via shift()"); x.shift(); console.log("updated array: %o", x.slice()); })(); 

See Object. defineProperty() Object. defineProperty() for reference.

This brings us closer, but it’s not proof of the bullet yet ... which leads us to:

3. Trusted

In the future, 1 proxy may offer another solution ... allowing you to intercept method calls, accessors, etc. Most importantly, you can do this without even specifying the explicit name of the property ..., which will allow you to test random access / assignment by index. You can even intercept the removal of property. Proxies effectively allowed you to check for changes before deciding on it ... in addition to processing the change after the fact.

Here's a trimmed sample:

 (function() { if (!("Proxy" in window)) { console.warn("Your browser doesn't support Proxies."); return; } // our backing array var array = ["a", "b", "c", "d"]; // a proxy for our array var proxy = new Proxy(array, { apply: function(target, thisArg, argumentsList) { return thisArg[target].apply(this, argumentList); }, deleteProperty: function(target, property) { console.log("Deleted %s", property); return true; }, set: function(target, property, value, receiver) { target[property] = value; console.log("Set %s to %o", property, value); return true; } }); console.log("Set a specific index.."); proxy[0] = "x"; console.log("Add via push()..."); proxy.push("z"); console.log("Add/remove via splice()..."); proxy.splice(1, 3, "y"); console.log("Current state of array: %o", array); })(); 

1 Browser support is getting much better, but there are some notable flaws.

+143
Feb 24 '11 at 4:40
source share

From all the answers read here, I have compiled a simplified solution that does not require any external libraries.

It also greatly improves the general idea of ​​the approach:

 function processQ() { // ... this will be called on each .push } var myEventsQ = []; myEventsQ.push = function() { Array.prototype.push.apply(this, arguments); processQ();}; 
+15
Jul 22 '15 at 19:45
source share

I found the following that seems to do the following: https://github.com/mennovanslooten/Observable-Arrays

Observable-Arrays extends the underline and can be used as follows: (from this page)

 // For example, take any array: var a = ['zero', 'one', 'two', 'trhee']; // Add a generic observer function to that array: _.observe(a, function() { alert('something happened'); }); 
+12
Nov 10
source share
 if (!Array.prototype.forEach) { Object.defineProperty(Array.prototype, 'forEach', { enumerable: false, value: function(callback) { for(var index = 0; index != this.length; index++) { callback(this[index], index, this); } } }); } if(Object.observe) { Object.defineProperty(Array.prototype, 'Observe', { set: function(callback) { Object.observe(this, function(changes) { changes.forEach(function(change) { if(change.type == 'update') { callback(); } }); }); } }); } else { Object.defineProperties(Array.prototype, { onchange: { enumerable: false, writable: true, value: function() { } }, Observe: { set: function(callback) { Object.defineProperty(this, 'onchange', { enumerable: false, writable: true, value: callback }); } } }); var names = ['push', 'pop', 'reverse', 'shift', 'unshift']; names.forEach(function(name) { if(!(name in Array.prototype)) { return; } var pointer = Array.prototype[name]; Array.prototype[name] = function() { pointer.apply(this, arguments); this.onchange(); } }); } var a = [1, 2, 3]; a.Observe = function() { console.log("Array changed!"); }; a.push(8); 
0
Dec 05 '14 at 17:32
source share

An interesting collection library https://github.com/mgesmundo/smart-collection . Allows you to view arrays and add them to them. Not sure about performance, as I test it myself. Will update this post soon.

0
Jan 30 '15 at 13:15
source share

I would not recommend you distribute your own prototypes. Instead, you can use a library like new-list; https://github.com/azer/new-list

It creates its own JavaScript array and allows you to subscribe to any changes. It includes updates and gives the final diff;

 List = require('new-list') todo = List('Buy milk', 'Take shower') todo.pop() todo.push('Cook Dinner') todo.splice(0, 1, 'Buy Milk And Bread') todo.subscribe(function(update){ // or todo.subscribe.once update.add // => { 0: 'Buy Milk And Bread', 1: 'Cook Dinner' } update.remove // => [0, 1] }) 
-5
Apr 17 '13 at 21:01
source share



All Articles