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());
})();
set(index)
en el prototipo de Array y hacer algo como dice la antisanidadDespués de leer todas las respuestas aquí, he reunido una solución simplificada que no requiere bibliotecas externas.
También ilustra mucho mejor la idea general del enfoque:
fuente
push
devuelve ellength
de la matriz. Por lo tanto, puede obtener el valor devuelto porArray.prototype.push.apply
a una variable y devolverlo desde lapush
función personalizada .Encontré lo siguiente que parece lograr esto: https://github.com/mennovanslooten/Observable-Arrays
Las matrices observables extienden el subrayado y se pueden usar de la siguiente manera: (de esa página)
fuente
arr[2] = "foo"
, la notificación de cambio es asincrónica . Dado que JS no proporciona ninguna forma de observar dichos cambios, esta biblioteca se basa en un tiempo de espera que se ejecuta cada 250 ms y verifica si la matriz ha cambiado en absoluto, por lo que no recibirá una notificación de cambio hasta el próximo tiempo se agota el tiempo de espera.push()
Sin embargo, otros cambios como recibir notificaciones de inmediato (sincrónicamente).Usé el siguiente código para escuchar los cambios en una matriz.
Espero que esto haya sido útil :)
fuente
La solución de método push Override más votada por @canon tiene algunos efectos secundarios que fueron inconvenientes en mi caso:
Hace que el descriptor de la propiedad push sea diferente (
writable
yconfigurable
debe establecerse entrue
lugar defalse
), lo que provoca excepciones en un punto posterior.Genera el evento varias veces cuando
push()
se llama una vez con varios argumentos (comomyArray.push("a", "b")
), lo que en mi caso era innecesario y malo para el rendimiento.Así que esta es la mejor solución que pude encontrar que soluciona los problemas anteriores y, en mi opinión, es más limpia / simple / más fácil de entender.
Consulte los comentarios de mis fuentes y sugerencias sobre cómo implementar las otras funciones mutantes además de push: 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'.
fuente
...
sintaxis similar y que se puede reemplazar fácilmente con el uso de laarguments
palabra clave.fuente
Object.observe()
yArray.observe()
se retiraron de la especificación. Ya se ha retirado el soporte de Chrome. : /No estoy seguro de si esto cubre absolutamente todo, pero uso algo como esto (especialmente al depurar) para detectar cuando una matriz tiene un elemento agregado:
fuente
Una biblioteca de colecciones interesante es https://github.com/mgesmundo/smart-collection . Le permite ver matrices y agregarles vistas también. No estoy seguro del rendimiento, ya que lo estoy probando yo mismo. Actualizará esta publicación pronto.
fuente
Jugueteé y se me ocurrió esto. La idea es que el objeto tenga todos los métodos Array.prototype definidos, pero los ejecute en un objeto de matriz separado. Esto da la capacidad de observar métodos como shift (), pop (), etc. Aunque algunos métodos como concat () no devolverán el objeto OArray. La sobrecarga de esos métodos no hará que el objeto sea observable si se utilizan descriptores de acceso. Para lograr esto último, los accesos se definen para cada índice dentro de la capacidad dada.
En cuanto al rendimiento ... OArray es entre 10 y 25 veces más lento en comparación con el objeto Array simple. Para la capaidad en un rango de 1 a 100, la diferencia es 1x-3x.
fuente
No le recomendaría ampliar los prototipos nativos. En su lugar, puede utilizar una biblioteca como new-list;https://github.com/azer/new-list
Crea una matriz de JavaScript nativa y le permite suscribirse a cualquier cambio. Recopila las actualizaciones y le da la diferencia final;
fuente