Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>There are a few options...</p> <h2>1. Override the push method</h2> <p>Going the quick and dirty route, you could override the <code>push()</code> method for your array<sup>1</sup>:</p> <pre><code>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 &lt; l; i++, n++) { RaiseMyEvent(this, n, this[n] = arguments[i]); // assign/raise your event } return n; } }); </code></pre> <p><sub><sup>1</sup> Alternatively, if you'd like to target <em>all</em> arrays, you could override <code>Array.prototype.push()</code>. Use caution, though; other code in your environment may not like or expect that kind of modification. Still, if a catch-all sounds appealing, just replace <code>myArray</code> with <code>Array.prototype</code>.</sub></p> <p>Now, that's just one method and there are lots of ways to change array content. We probably need something more comprehensive...</p> <h2>2. Create a custom observable array</h2> <p>Rather than overriding methods, you could create your own observable array. This particular implementation copies an array into a new array-like object and provides custom <code>push()</code>, <code>pop()</code>, <code>shift()</code>, <code>unshift()</code>, <code>slice()</code>, and <code>splice()</code> methods <strong>as well as</strong> custom index accessors (provided that the array size is only modified via one of the aforementioned methods or the <code>length</code> property).</p> <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false"> <div class="snippet-code"> <pre class="snippet-code-js lang-js prettyprint-override"><code>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 &gt;= 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 &lt; 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 &gt; -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 &lt; ln; i++) { _array.splice(i, 0, arguments[i]); defineIndexProperty(_array.length - 1); raiseEvent({ type: "itemadded", index: i, item: arguments[i] }); } for (; i &lt; _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 &gt; -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 &lt; 0 ? _array.length + index : index; howMany = howMany == null ? _array.length - index : howMany &gt; 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 &lt; 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 &amp;&amp; n &gt;= 0) { if (n &lt; length) { _self.splice(n); } else if (n &gt; 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()); })();</code></pre> </div> </div> </p> <p><sub>See <code>Object.<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty" rel="nofollow noreferrer">defineProperty()</a></code> for reference.</sub></p> <p>That gets us closer but it's still not bullet proof... which brings us to:</p> <h2>3. Proxies</h2> <p>In the future<sup>1</sup>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy" rel="nofollow noreferrer">proxies</a> may offer another solution... allowing you to intercept method calls, accessors, etc. Most importantly, you can do this without even providing an explicit property name... which would allow you to test for an arbitrary, index-based access/assignment. You can even intercept property deletion. Proxies would effectively allow you to inspect a change <em>before</em> deciding to allow it... in addition to handling the change after the fact.</p> <p>Here's a stripped down sample:</p> <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false"> <div class="snippet-code"> <pre class="snippet-code-js lang-js prettyprint-override"><code>(function() { if (!("Proxy" in window)) { console.warn("Your browser doesn't support Proxies."); return; } // our backing array var array = ["a", "b", "c", "d"]; // a proxy for 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 to %o", property, value); return true; } }); console.log("Set a 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); })();</code></pre> </div> </div> </p> <p><sub><sup>1</sup> <a href="http://caniuse.com/#feat=proxy" rel="nofollow noreferrer">Browser support</a> is getting much better but there are still some noticeable holes.</sub></p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload