javascript - How to watch for array changes? -
in javascript, there way notified when array modified using push, pop, shift or index-based assignment? want fire event handle.
i know watch() functionality in spidermonkey, works when entire variable set else.
there few options...
1. override push method
going quick , dirty route, override push()
method array1:
object.defineproperty(myarray, "push", { configurable: false, enumerable: false, // hide for...in writable: false, value: function () { (var = 0, n = this.length, l = arguments.length; < l; i++, n++) { raisemyevent(this, n, this[n] = arguments[i]); // assign/raise event } return n; } });
1 alternatively, override array.prototype.push()
... though, messing array.prototype
ill-advised. still, if you'd rather that, replace myarray
array.prototype
.
now, that's 1 method , there lots of ways change array content. need more comprehensive...
2. create custom observable array
rather overriding methods, create own observable array. particular implementation copies array new array-like object , provides custom push()
, pop()
, shift()
, unshift()
, slice()
, , splice()
methods as as custom index accessors (provided array size modified via 1 of aforementioned methods or 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; (var = 0, ln = arguments.length; < 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() { (var = 0, ln = arguments.length; < ln; i++) { _array.splice(i, 0, arguments[i]); defineindexproperty(_array.length - 1); raiseevent({ type: "itemadded", index: i, item: arguments[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 }); } (var = 2, ln = arguments.length; < 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 @ index %d.", e.item, e.index); }); x.addeventlistener("itemset", function(e) { console.log("set index %d %o.", e.index, e.item); }); x.addeventlistener("itemremoved", function(e) { console.log("removed %o @ index %d.", e.item, e.index); }); console.log("popping , 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 10..."); x.length = 10; console.log("updated array: %o", x.slice()); console.log("setting length 2..."); x.length = 2; console.log("extracting first element via shift()"); x.shift(); console.log("updated array: %o", x.slice()); })();
see object.defineproperty()
reference.
that gets closer it's still not bullet proof... brings to:
3. proxies
in future1, proxies may offer solution... allowing intercept method calls, accessors, etc. importantly, can without providing explicit property name... allow test arbitrary, index-based access/assignment. can intercept property deletion. proxies allow inspect change before deciding allow it... in addition handling change after fact.
here's stripped down sample:
(function() { if (!("proxy" in window)) { console.warn("your browser doesn't support proxies."); return; } // our backing array var array = ["a", "b", "c", "d"]; // proxy 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 %o", property, value); return true; } }); console.log("set 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 getting better there still noticeable holes.
Comments
Post a Comment