tvcutsem/harmony-reflect

how to trap prototype method calls of ES6 class object?

xmlking opened this issue ยท 5 comments

Give ES6 class, how to trap prototype method calls?

class Account {
    constructor(name,  amount) {
        this.name = name;
        this.amount = amount;
    }
    withdraw(arg) {
        this.amount = this.amount - arg;
    }

    deposit(arg) {
        this.amount = this.amount + arg;
    }
}
var proxyAccount = new Proxy(new Account('sumo', 500) , {} );
proxyAccount.deposit(100); //want to trap this method call
proxyAccount.withdraw(50);

ES6 class methods are roughly equivalent to ES5 functions that live on the prototype object, i.e. Account.prototype. You can see this clearly by "compiling away" the class notation, using e.g. TypeScript's playground.

Method calls like proxyAccount.deposit(100) would trigger the get trap like any other method call. In fact, for the Proxy object, it doesn't matter whether it is wrapping a normal ES5 object or an ES6 class instance: from the Proxy's perspective, both are plain Javascript objects.

thanks @tvcutsem
So I have to do something like this? is there a better solution?

var methodName
var proxyAccount = new Proxy (new Account('sumo', 500) , {
    get: function (target, name, receiver) {
        console.log('get called for field: ', name);
        if(!target.hasOwnProperty(name))
        {
           // do I have to store `name` into global variable, to reference it in `apply trap?    
           // methodName = name
            return new Proxy(target[name],this);
        }
        return Reflect.get(target, name, receiver);
    },
    apply: (target,receiver,args) => {
        console.log('methodName: ',  'I need methodName here. how do I get  -withdraw- here?');
        //TODO do some before-method call task here
        return Reflect.apply(target, receiver, args);
    }
});

proxyAccount.deposit(100);  
proxyAccount.withdraw(50);

I have few more questions:

  1. I also need method name in apply trap. how can I get the name of the target function? i.g deposit
  2. like Object.getPropertyNames(obj) , are there any similar Object.getMethodNames(obj) helper method in reflect API? I need to get all prototype method names on class instance.

at present I am using following code snippet:

var methodNames = [];
for(let prop in obj) {
    if(!obj.hasOwnProperty(prop)) {
        methodNames.push(prop);
    }
}
console.log('all methodNames: ', methodNames);

Regarding 1) I think you are misunderstanding how method invocation works on proxies. Method invocation does not trigger the apply trap. apply is only used when a proxy is called as a function, like:

var p = new Proxy(target, handler)
p(1,2,3) // calls handler.apply(target, undefined, [1,2,3])

The way method invocation works is as follows:

var p = new Proxy(target, handler)
p.foo(1,2,3) // calls handler.get(target, foo") and expects it to return a function f
// then calls f.call(p, 1, 2, 3)

(see https://github.com/tvcutsem/harmony-reflect/blob/master/doc/traps.md for more details)

So, if you want to intercept a method call on an ES6 class instance, I would do something like the following:

var account = new Account(...)
var p = new Proxy(account, {
  get: function(target, name, receiver) {
    if (name in target.__proto__) { // assume methods live on the prototype
      return function(...args) {
         var methodName = name;
         // we now have access to both methodName and arguments
      };
    } else { // assume instance vars like on the target
        return Reflect.get(target, name, receiver);
    }
}
  1. There is no utility method that returns all the names of only inherited properties, but it's easy enough to write this yourself. You could actually use just Object.getOwnPropertyNames(Object.getPrototypeOf(obj))

Note that JavaScript does not impose any restrictions on what is a "method" or where methods have to live. ES6 classes use a particular pattern where methods are function properties that live on the prototype, but this is just a pattern. In general, you can define objects that e.g. define methods as "own" properties.

Hope this helps,
Tom

Cool to see you are experimenting with both ES6 classes and proxies. Cutting-edge JS ;-)