Error Summary

Before I screamed for trying something so reckless, let me tell you that I will not do this in real life, and this is an academic question.

Suppose I'm writing a library and want my object to be able to compose methods as needed.

For example, if you want to call the .slice() method, and I don't have one, then the window.onerror handler will fire it for me

Anyway I played with this here

 window.onerror = function(e) { var method = /'(.*)'$/.exec(e)[1]; console.log(method); // slice return Array.prototype[method].call(this, arguments); // not even almost gonna work }; var myLib = function(a, b, c) { if (this == window) return new myLib(a, b, c); this[1] = a; this[2] = b; this[3] = c; return this; }; var obj = myLib(1,2,3); console.log(obj.slice(1)); 

Also (maybe I should start a new question) can I change my constructor to get an indefinite number of arguments?

 var myLib = function(a, b, c) { if (this == window) return new myLib.apply(/* what goes here? */, arguments); this[1] = a; this[2] = b; this[3] = c; return this; }; 

By the way, I know that I can load my objects with

 ['slice', 'push', '...'].forEach(function() { myLib.prototype[this] = [][this]; }); 

This is not what I'm looking for

+6
source share
1 answer

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.

proxy diagram

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); } }; 
+4
source

Source: https://habr.com/ru/post/907412/


All Articles