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)
w prototypie Arraya i zrobić coś takiego, jak mówi antyspołecznośćCzytając wszystkie odpowiedzi tutaj, zebrałem uproszczone rozwiązanie, które nie wymaga żadnych zewnętrznych bibliotek.
To również znacznie lepiej ilustruje ogólną ideę podejścia:
źródło
push
zwracalength
tablicę. Możesz więc pobrać wartość zwracaną przezArray.prototype.push.apply
zmienną i zwrócić ją zpush
funkcji niestandardowej .Znalazłem następujące rzeczy, które wydają się to osiągnąć: https://github.com/mennovanslooten/Observable-Arrays
Observable-Arrays rozszerza podkreślenie i może być użyte w następujący sposób: (z tej strony)
źródło
arr[2] = "foo"
, powiadomienie o zmianie jest asynchroniczne . Ponieważ JS nie zapewnia żadnego sposobu na obserwowanie takich zmian, ta biblioteka opiera się na limicie czasu, który jest uruchamiany co 250 ms i sprawdza, czy tablica w ogóle się zmieniła - więc nie otrzymasz powiadomienia o zmianie do następnego czas upływa limitu czasu.push()
Jednak inne zmiany, takie jak natychmiastowe otrzymywanie powiadomień (synchronicznie).Użyłem następującego kodu, aby nasłuchiwać zmian w tablicy.
Mam nadzieję, że to było przydatne :)
źródło
Najpopularniejsze rozwiązanie metody Override push autorstwa @canon ma pewne skutki uboczne, które były niewygodne w moim przypadku:
To sprawia, że deskryptor właściwości push jest inny (
writable
iconfigurable
powinien być ustawionytrue
zamiastfalse
), co powoduje wyjątki w późniejszym punkcie.Wywołuje zdarzenie wiele razy, gdy
push()
jest wywoływane raz z wieloma argumentami (takimi jakmyArray.push("a", "b")
), co w moim przypadku było niepotrzebne i niekorzystne dla wydajności.Jest to więc najlepsze rozwiązanie, jakie znalazłem, które rozwiązuje poprzednie problemy i jest moim zdaniem czystsze / prostsze / łatwiejsze do zrozumienia.
Proszę zapoznać się z komentarzami do moich źródeł i wskazówkami, jak zaimplementować inne funkcje mutujące oprócz push: „pop”, „shift”, „unshift”, „splice”, „sort”, „reverse”.
źródło
...
składnię i można go łatwo zastąpić za pomocąarguments
słowa kluczowego.źródło
Object.observe()
iArray.observe()
zostały wycofane ze specyfikacji. Wsparcie zostało już ściągnięte z Chrome. : /Nie jestem pewien, czy obejmuje to absolutnie wszystko, ale używam czegoś takiego (szczególnie podczas debugowania), aby wykryć, kiedy tablica ma dodany element:
źródło
Ciekawą biblioteką kolekcji jest https://github.com/mgesmundo/smart-collection . Umożliwia oglądanie tablic i dodawanie do nich widoków. Nie jestem pewien wydajności, ponieważ sam ją testuję. Niedługo zaktualizuję ten post.
źródło
Bawiłem się i wymyśliłem to. Chodzi o to, że obiekt ma zdefiniowane wszystkie metody Array.prototype, ale wykonuje je na oddzielnym obiekcie tablicy. Daje to możliwość obserwowania metod takich jak shift (), pop () itp. Chociaż niektóre metody, takie jak concat (), nie zwracają obiektu OArray. Przeciążenie tych metod nie spowoduje, że obiekt będzie obserwowalny, jeśli używane są metody dostępu. Aby to osiągnąć, metody dostępu są definiowane dla każdego indeksu w ramach określonej pojemności.
Mądra wydajność ... OArray jest około 10-25 razy wolniejszy niż zwykły obiekt Array. Dla pojemności w zakresie 1 - 100 różnica wynosi 1x-3x.
źródło
Nie polecałbym ci rozszerzania natywnych prototypów. Zamiast tego możesz użyć biblioteki takiej jak nowa-lista; https://github.com/azer/new-list
Tworzy natywną tablicę JavaScript i umożliwia subskrybowanie dowolnej zmiany. Grupuje aktualizacje i podaje ostateczną różnicę;
źródło