Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an equivalent of the __noSuchMethod__ feature for properties, or a way to implement it in JS?

Tags:

javascript

There is a noSuchMethod feature in some javascript implementations (Rhino, SpiderMonkey)

proxy = {
    __noSuchMethod__: function(methodName, args){
        return "The " + methodName + " method isn't implemented yet. HINT: I accept cash and beer bribes" ;
    },

    realMethod: function(){
     return "implemented" ;   
    }
}

js> proxy.realMethod()
implemented
js> proxy.newIPod()
The newIPod method isn't implemented yet. HINT: I accept cash and beer bribes
js>

I was wondering, is there was a way to do something similar for properties? I'd like to write proxy classes that can dispatch on properties as well as methods.

like image 802
TomSW Avatar asked Feb 15 '10 15:02

TomSW


2 Answers

UPDATE: ECMAScript 6 Proxies are widely supported now. Basically, if you don't need to support IE11, you can use them.

Proxy objects allow you to define custom behavior for fundamental operations, like property lookup, assignment, enumeration, function invocation, etc.

Emulating __noSuchMethod__ with ES6 Proxies

By implementing traps on property access, you can emulate the behavior of the non-standard __noSuchMethod__ trap:

function enableNoSuchMethod(obj) {
  return new Proxy(obj, {
    get(target, p) {
      if (p in target) {
        return target[p];
      } else if (typeof target.__noSuchMethod__ == "function") {
        return function(...args) {
          return target.__noSuchMethod__.call(target, p, args);
        };
      }
    }
  });
}

// Example usage:

function Dummy() {
  this.ownProp1 = "value1";
  return enableNoSuchMethod(this);
}

Dummy.prototype.test = function() {
  console.log("Test called");
};

Dummy.prototype.__noSuchMethod__ = function(name, args) {
  console.log(`No such method ${name} called with ${args}`);
  return;
};

var instance = new Dummy();
console.log(instance.ownProp1);
instance.test();
instance.someName(1, 2);
instance.xyz(3, 4);
instance.doesNotExist("a", "b");

Original 2010 answer

There is only one existing thing at the moment that can actually do what you want, but unfortunately is not widely implemented:

  • ECMAScript Harmony Proxies.

There are only two working implementations available at this time, in the latest Firefox 4 betas (it has been around since FF3.7 pre-releases) and in node-proxy for server-side JavaScript -Chrome and Safari are currently working on it-.

It is one of the early proposals for the next version of ECMAScript, it's an API that allows you to implement virtualized objects (proxies), where you can assign a variety of traps -callbacks- that are executed in different situations, you gain full control on what at this time -in ECMAScript 3/5- only host objects could do.

To build a proxy object, you have to use the Proxy.create method, since you are interested in the set and get traps, I leave you a really simple example:

var p = Proxy.create({
  get: function(proxy, name) {        // intercepts property access
    return 'Hello, '+ name;
  },
  set: function(proxy, name, value) { // intercepts property assignments
    alert(name +'='+ value);
    return true;
  }
});

alert(p.world); // alerts 'Hello, world'
p.foo = 'bar';  // alerts foo=bar

Try it out here.

EDIT: The proxy API evolved, the Proxy.create method was removed in favor of using the Proxy constructor, see the above code updated to ES6:

const obj = {};
const p = new Proxy(obj, {
  get(target, prop) {        // intercepts property access
    return 'Hello, '+ prop;
  },
  set(target, prop, value, receiver) { // intercepts property assignments
    console.log(prop +'='+ value);
    Reflect.set(target, prop, value, receiver)
    return true;
  }
});

console.log(p.world);
p.foo = 'bar';

The Proxy API is so new that isn't even documented on the Mozilla Developer Center, but as I said, a working implementation has been included since the Firefox 3.7 pre-releases.

The Proxy object is available in the global scope and the create method can take two arguments, a handler object, which is simply an object that contains properties named as the traps you want to implement, and an optional proto argument, that makes you able to specify an object that your proxy inherits from.

The traps available are:

// TrapName(args)                          Triggered by
// Fundamental traps
getOwnPropertyDescriptor(name):           // Object.getOwnPropertyDescriptor(proxy, name)
getPropertyDescriptor(name):              // Object.getPropertyDescriptor(proxy, name) [currently inexistent in ES5]
defineProperty(name, propertyDescriptor): // Object.defineProperty(proxy,name,pd)
getOwnPropertyNames():                    // Object.getOwnPropertyNames(proxy) 
getPropertyNames():                       // Object.getPropertyNames(proxy) 
delete(name):                             // delete proxy.name
enumerate():                              // for (name in proxy)
fix():                                    // Object.{freeze|seal|preventExtensions}(proxy)

// Derived traps
has(name):                                // name in proxy
hasOwn(name):                             // ({}).hasOwnProperty.call(proxy, name)
get(receiver, name):                      // receiver.name
set(receiver, name, val):                 // receiver.name = val
keys():                                   // Object.keys(proxy)

The only resource I've seen, besides the proposal by itself, is the following tutorial:

  • Harmony Proxies: Tutorial

Edit: More information is coming out, Brendan Eich recently gave a talk at the JSConf.eu Conference, you can find his slides here:

  • Proxies are Awesome!
like image 126
Christian C. Salvadó Avatar answered Nov 06 '22 08:11

Christian C. Salvadó


Here's how to get behaviour similar to __noSuchMethod__

First of all, here's a simple object with one method:

var myObject = {
    existingMethod: function (param) {
        console.log('existing method was called', param);
    }
}

Now create a Proxy which will catch access to properties/method and add your existing object as a first parameter.

var myObjectProxy = new Proxy(myObject, {
   get: function (func, name) {
       // if property or method exists, return it
       if( name in myObject ) {
           return myObject[name];
       }
       // if it doesn't exists handle non-existing name however you choose
       return function (args) {
           console.log(name, args);
       }
    }
});

Now try it:

myObjectProxy.existingMethod('was called here');
myObjectProxy.nonExistingMethod('with a parameter');

Works in Chrome/Firefox/Opera. Doesn't work in IE(but already works in Edge). Also tested on mobile Chrome.

Creation of proxy can be automated and invisible i.e. if you use Factory pattern to build your objects. I did that to create workers which internal functions can be called directly from the main thread. Using workers can be now so simple thanks to this cool new feature called Proxy. The simplest worker implementation ever:

var testWorker = createWorker('pathTo/testWorker.js');
testWorker.aFunctionInsideWorker(params, function (result) {
    console.log('results from worker: ', result);
});
like image 7
Pawel Avatar answered Nov 06 '22 06:11

Pawel