Come guardare le modifiche alla matrice?

In Javascript, c’è un modo per essere avvisati quando un array viene modificato usando push, pop, shift o assegnazione basata su indici? Voglio qualcosa che spari un evento che potrei gestire.

Conosco le funzionalità di watch () in SpiderMonkey, ma funziona solo quando l’intera variabile è impostata su qualcos’altro.

Ci sono alcune opzioni …

1. Sostituire il metodo push

Procedendo in modo rapido e sporco, è ansible sovrascrivere il metodo push() per l’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 In alternativa, se desideri scegliere come target tutti gli array, puoi eseguire l'override di Array.prototype.push() . Usa caucanvas, però; altro codice nel tuo ambiente potrebbe non piacere o prevedere quel tipo di modifica. Tuttavia, se un acctriggersnte sembra attraente, basta sostituire myArray con Array.prototype .

Ora, questo è solo un metodo e ci sono molti modi per cambiare il contenuto dell'array. Probabilmente abbiamo bisogno di qualcosa di più completo ...

2. Creare un array osservabile personalizzato

Piuttosto che sovrascrivere i metodi, è ansible creare il proprio array osservabile. Questa particolare implementazione copia una matrice in un nuovo object simile a una matrice e fornisce metodi personalizzati push() , pop() , shift() , unshift() , slice() e splice() nonché accessorizzatori di indici personalizzati (a condizione che la dimensione dell'array viene modificata solo tramite uno dei metodi sopra menzionati o la proprietà length ).

 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()); })(); 

Ho trovato il seguente che sembra realizzare questo: https://github.com/mennovanslooten/Observable-Arrays

Le Array osservabili estendono il trattino basso e possono essere utilizzate come segue: (da quella pagina)

 // 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'); }); 

Dalla lettura di tutte le risposte qui, ho assemblato una soluzione semplificata che non richiede alcuna libreria esterna.

Illustra anche molto meglio l’idea generale per l’approccio:

 function processQ() { // ... this will be called on each .push } var myEventsQ = []; myEventsQ.push = function() { Array.prototype.push.apply(this, arguments); processQ();}; 

Ho usato il seguente codice per ascoltare le modifiche a un array.

 /* @arr array you want to listen to @callback function that will be called on any change inside array */ function listenChangesinArray(arr,callback){ // Add more methods here if you want to listen to them ['pop','push','reverse','shift','unshift','splice','sort'].forEach((m)=>{ arr[m] = function(){ var res = Array.prototype[m].apply(arr, arguments); // call normal behaviour callback.apply(arr, arguments); // finally call the callback supplied return res; } }); } 

Spero che questo è stato utile 🙂

 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); 

Una raccolta di raccolte interessante è https://github.com/mgesmundo/smart-collection . Ti consente di guardare gli array e di aggiungere viste anche a loro. Non sono sicuro delle prestazioni come sto testando da solo. Aggiornerà presto questo post.

Non sono sicuro se questo copre assolutamente tutto, ma io uso qualcosa come questo (specialmente quando si esegue il debug) per rilevare quando un array ha un elemento aggiunto:

 var array = [1,2,3,4]; array = new Proxy(array, { set: function(target, key, value) { if (Number.isInteger(Number(key)) || key === 'length') { debugger; //or other code } target[key] = value; return true; } }); 

Ho trafficato e ho inventato questo. L’idea è che l’object abbia tutti i metodi Array.prototype definiti, ma li esegue su un object array separato. Questo dà la possibilità di osservare metodi come shift (), pop () ecc. Sebbene alcuni metodi come concat () non restituiscano l’object OArray. Il sovraccarico di questi metodi non renderà l’object osservabile se vengono utilizzati gli accessor. Per raggiungere quest’ultimo, gli accessor sono definiti per ogni indice all’interno di una determinata capacità.

Prestazioni … OArray è circa 10-25 volte più lento rispetto al semplice object Array. Per la capa- cità in un intervallo da 1 a 100 la differenza è 1x-3x.

 class OArray { constructor(capacity, observer) { var Obj = {}; var Ref = []; // reference object to hold values and apply array methods if (!observer) observer = function noop() {}; var propertyDescriptors = Object.getOwnPropertyDescriptors(Array.prototype); Object.keys(propertyDescriptors).forEach(function(property) { // the property will be binded to Obj, but applied on Ref! var descriptor = propertyDescriptors[property]; var attributes = { configurable: descriptor.configurable, enumerable: descriptor.enumerable, writable: descriptor.writable, value: function() { observer.call({}); return descriptor.value.apply(Ref, arguments); } }; // exception to length if (property === 'length') { delete attributes.value; delete attributes.writable; attributes.get = function() { return Ref.length }; attributes.set = function(length) { Ref.length = length; }; } Object.defineProperty(Obj, property, attributes); }); var indexerProperties = {}; for (var k = 0; k < capacity; k++) { indexerProperties[k] = { configurable: true, get: (function() { var _i = k; return function() { return Ref[_i]; } })(), set: (function() { var _i = k; return function(value) { Ref[_i] = value; observer.call({}); return true; } })() }; } Object.defineProperties(Obj, indexerProperties); return Obj; } } 

Non ti consiglierei di estendere i prototipi nativi. Invece, puoi usare una libreria come nuova lista; https://github.com/azer/new-list

Crea una matrice JavaScript nativa e ti consente di iscriverti a qualsiasi modifica. Mette in batch gli aggiornamenti e ti dà il diff finale;

 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] })