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)
no protótipo do Array e fazer algo como o anti-sanidade dizAo ler todas as respostas aqui, reuni uma solução simplificada que não requer nenhuma biblioteca externa.
Também ilustra muito melhor a ideia geral da abordagem:
fonte
push
retorna olength
da matriz. Portanto, você pode obter o valor retornado porArray.prototype.push.apply
para uma variável e retorná-lo dapush
função personalizada .Encontrei o seguinte que parece cumprir isso: https://github.com/mennovanslooten/Observable-Arrays
Observable-Arrays estende o sublinhado e pode ser usado da seguinte maneira: (a partir dessa página)
fonte
arr[2] = "foo"
, a notificação de mudança é assíncrona . Uma vez que JS não fornece nenhuma maneira de observar tais mudanças, esta biblioteca depende de um tempo limite que é executado a cada 250 ms e verifica se o array mudou de alguma forma - então você não receberá uma notificação de mudança até o próximo tempo em que o tempo limite é executado. Outras mudanças, comopush()
ser notificado imediatamente (de forma síncrona), no entanto.Usei o código a seguir para ouvir as alterações em uma matriz.
Espero que tenha sido útil :)
fonte
A solução de método push Override mais votada por @canon tem alguns efeitos colaterais que eram inconvenientes no meu caso:
Isso torna o descritor da propriedade push diferente (
writable
econfigurable
deve ser definido emtrue
vez defalse
), o que causa exceções em um ponto posterior.Ele gera o evento várias vezes quando
push()
é chamado uma vez com vários argumentos (comomyArray.push("a", "b")
), o que no meu caso era desnecessário e ruim para o desempenho.Portanto, esta é a melhor solução que encontrei para corrigir os problemas anteriores e é na minha opinião mais limpa / simples / fácil de entender.
Por favor, veja os comentários das minhas fontes e dicas sobre como implementar as outras funções mutantes além de push: 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'.
fonte
...
sintaxe semelhante e que pode ser facilmente substituído com o uso daarguments
palavra - chave.fonte
Object.observe()
eArray.observe()
foi retirado da especificação. O suporte já foi retirado do Chrome. : /Não tenho certeza se isso cobre absolutamente tudo, mas eu uso algo assim (especialmente durante a depuração) para detectar quando uma matriz tem um elemento adicionado:
fonte
Uma biblioteca de coleção interessante é https://github.com/mgesmundo/smart-collection . Permite que você observe arrays e também adicione visualizações a eles. Não tenho certeza sobre o desempenho, pois estou testando sozinho. Irá atualizar esta postagem em breve.
fonte
Eu brinquei e descobri isso. A ideia é que o objeto tenha todos os métodos Array.prototype definidos, mas os execute em um objeto array separado. Isso dá a capacidade de observar métodos como shift (), pop () etc. Embora alguns métodos como concat () não retornem o objeto OArray. Sobrecarregar esses métodos não tornará o objeto observável se acessores forem usados. Para alcançar o último, os acessores são definidos para cada índice dentro de uma determinada capacidade.
Em termos de desempenho ... OArray é cerca de 10-25 vezes mais lento em comparação com o objeto Array simples. Para a capacidade em um intervalo de 1 a 100, a diferença é 1x-3x.
fonte
Eu não recomendaria estender protótipos nativos. Em vez disso, você pode usar uma biblioteca como new-list; https://github.com/azer/new-list
Ele cria uma matriz JavaScript nativa e permite que você assine qualquer alteração. Ele agrupa as atualizações e fornece a diferença final;
fonte