Since you asked an academic question, I believe that browser compatibility is not a problem. If this is really not the case, I would like to introduce consonant proxies for this. onerror
is not a good practice, because it is just an event that occurred if there is an error somewhere. It should, if ever, be used only as a last resort. (I know that you said you weren't using it anyway, but onerror
just not very developer friendly.)
Basically, proxies allow you to intercept most of the basic operations in JavaScript - it is especially important to get some property that is useful here. In this case, you can intercept the .slice
retrieval .slice
.
Please note that proxies are black holes by default. They do not correspond to any object (for example, setting a property in a proxy just calls the set
hook (interceptor), the actual save you have to do yourself). But there is an available “forwarding handler” that redirects everything to a regular object (or, of course, an instance), so the proxy behaves like a regular object. By extending the handler (in this case, the get
part), you can easily route the Array.prototype
methods as follows.
So, whenever you select a property (named name
), the code path looks like this:
- Try returning
inst[name]
. - Otherwise, try returning a function that applies
Array.prototype[name]
to the instance with the specified arguments for this function. - Otherwise, just return
undefined
.
If you want to play with proxies, you can use the latest version of V8, for example, in the nightly assembly of Chromium (make sure that it works like chrome --js-flags="--harmony"
). Again, proxies are not available for "normal" use, because they are relatively new, change many of the main parts of JavaScript, and are actually not yet officially indicated (more drafts).
This is a simple diagram of how this happens ( inst
is actually the proxy server to which the instance was moved). Note that this only illustrates getting the property; all other operations are simply transmitted by the proxy server due to the unmodified forwarding handler.

The proxy code may be as follows:
function Test(a, b, c) { this[0] = a; this[1] = b; this[2] = c; this.length = 3; // needed for .slice to work } Test.prototype.foo = "bar"; Test = (function(old) { // replace function with another function // that returns an interceptor proxy instead // of the actual instance return function() { var bind = Function.prototype.bind, slice = Array.prototype.slice, args = slice.call(arguments), // to pass all arguments along with a new call: inst = new(bind.apply(old, [null].concat(args))), // ^ is ignored because of `new` // which forces `this` handler = new Proxy.Handler(inst); // create a forwarding handler // for the instance handler.get = function(receiver, name) { // overwrite `get` handler if(name in inst) { // just return a property on the instance return inst[name]; } if(name in Array.prototype) { // otherwise try returning a function // that calls the appropriate method // on the instance return function() { return Array.prototype[name].apply(inst, arguments); }; } }; return Proxy.create(handler, Test.prototype); }; })(Test); var test = new Test(123, 456, 789), sliced = test.slice(1); console.log(sliced); // [456, 789] console.log("2" in test); // true console.log("2" in sliced); // false console.log(test instanceof Test); // true // (due to second argument to Proxy.create) console.log(test.foo); // "bar"
The forwarding handler is available on the Harmony official wiki page .
Proxy.Handler = function(target) { this.target = target; }; Proxy.Handler.prototype = { // Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined getOwnPropertyDescriptor: function(name) { var desc = Object.getOwnPropertyDescriptor(this.target, name); if (desc !== undefined) { desc.configurable = true; } return desc; }, // Object.getPropertyDescriptor(proxy, name) -> pd | undefined getPropertyDescriptor: function(name) { var desc = Object.getPropertyDescriptor(this.target, name); if (desc !== undefined) { desc.configurable = true; } return desc; }, // Object.getOwnPropertyNames(proxy) -> [ string ] getOwnPropertyNames: function() { return Object.getOwnPropertyNames(this.target); }, // Object.getPropertyNames(proxy) -> [ string ] getPropertyNames: function() { return Object.getPropertyNames(this.target); }, // Object.defineProperty(proxy, name, pd) -> undefined defineProperty: function(name, desc) { return Object.defineProperty(this.target, name, desc); }, // delete proxy[name] -> boolean delete: function(name) { return delete this.target[name]; }, // Object.{freeze|seal|preventExtensions}(proxy) -> proxy fix: function() { // As long as target is not frozen, the proxy won't allow itself to be fixed if (!Object.isFrozen(this.target)) { return undefined; } var props = {}; Object.getOwnPropertyNames(this.target).forEach(function(name) { props[name] = Object.getOwnPropertyDescriptor(this.target, name); }.bind(this)); return props; }, // == derived traps == // name in proxy -> boolean has: function(name) { return name in this.target; }, // ({}).hasOwnProperty.call(proxy, name) -> boolean hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); }, // proxy[name] -> any get: function(receiver, name) { return this.target[name]; }, // proxy[name] = value set: function(receiver, name, value) { this.target[name] = value; return true; }, // for (var name in proxy) { ... } enumerate: function() { var result = []; for (var name in this.target) { result.push(name); }; return result; }, // Object.keys(proxy) -> [ string ] keys: function() { return Object.keys(this.target); } };