[[Invoke]] and implicit method calls

Tom Van Cutsem tomvc.be at gmail.com
Wed Sep 18 05:28:38 PDT 2013


2013/9/17 Jason Orendorff <jason.orendorff at gmail.com>

> If that's all, it seems like we should definitely remove [[Invoke]]
> and the .invoke trap. The MOP was already complicated enough. The
> performance argument is a non-starter, and the other “feature” is
> entirely undesirable.
>

I'm trying to reconstruct Allen's motivation for including [[Invoke]],
which had to do with proxies that forward method calls to target objects
that depend on their this-binding to access private state.

Assume that we want to proxy the following target object:

var privateState = new WeakMap();
var target = { m: function() { return privateState.get(this); };
privateState.set(target, 42);
assert( target.m() === 42 );

// I need otherTarget to make a point later
var otherTarget = {};
privateState.set(otherTarget, 43);
assert( target.m.call(otherTarget) === 43 ); // we can still re-bind |this|

Now consider a naive forwarding proxy, written as:

var p1 = new Proxy(target, {
  get: function(target, name, receiver) {
    return target[name];
  }
});

Now: p1.m() === undefined // bad, because |this| inside target.m is bound
to proxy instead of target.
But: p1.m.call(otherTarget) === 43 // good, clients can still rebind |this|.

To solve the first problem, the proxy can be refined to eagerly bind any
method properties:

var p2 = new Proxy(target, {
  get: function(target, name, receiver) {
    var prop = target[name];
    if (typeof prop === "function") return prop.bind(target);
    return prop;
  }
});

Now: p2.m() === 42 // good
But: p2.m.call(otherTarget) !== 43 // bad, cannot re-bind |this| on
extracted properties

Adding an invoke() trap gives us a way to solve this:

var p3 = new Proxy(target, {
  get: function(target, name, receiver) {
    return target[name]; // no bind-on-extraction
  },
  invoke: function(target, name, args, receiver) {
    return target[name].apply(target, args);
  }
});

Now: p3.m() === 42 // good
And: p3.m.call(otherTarget) === 43 // good

I guess we could indeed drop the invoke() trap, if we are willing to use
the following, more intricate, pattern:

var p4 = new Proxy(target, {
  get: function(target, name, receiver) {
    var prop = target[name];
    if (typeof prop === "function") {
      return function(...args) {
        var self = (this === p4 ? target : this); // re-bind |this| only if
bound to the proxy, otherwise don't rebind
        return prop.apply(self, args);
      }
    }
    return prop;
  }
});

Now: p4.m() === 42 // good
And: p4.m.call(otherTarget) === 43 // good

AFAICT, performance arguments aside, the question of whether or not to
include invoke() boils down to whether you prefer the p3 or p4 pattern.

Cheers,
Tom
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130918/22bfe4c3/attachment.html>


More information about the es-discuss mailing list