Harmony Proxies: Tutorial

(Under construction)

The Proxy API enables the creation of dynamic proxies in Javascript. Dynamic proxies are useful for writing generic object or function wrappers or for creating virtual object abstractions. To avoid any confusion: these proxies have nothing to do with web proxy servers.

Perhaps the most compelling use of proxies is the ability to create objects whose properties can be computed dynamically. Proxies encompass all use cases that one would use __noSuchMethod__ for (such as creating mock objects in testing frameworks, Rails ActiveRecord dynamic finder-like capabilities, etc.) but generalize to operations other than method invocation.

What platforms support Proxies?

The Proxy API is scheduled to be standardized in the next version of ECMAScript (presumably ECMAScript 6). To see whether proxies are supported in your browser, check for the presence of a global object named Proxy. AFAICT, the only browser that currently supports Proxies is Firefox 4 or higher (see the MDN documentation). Proxies are being implemented for V8, Chrome's Javascript engine. Recent v8 shells (v8 3.5 or higher) supports proxies using the v8 --harmony-proxies flag.

There also exist a couple of node.js extensions supporting Proxies: node-overload provides a partial implementation of this API on V8, and node-proxy seems to provide a more complete implementation. The Caja ES5/3 effort, which supports ECMAScript 5 on ECMAScript 3 browsers, also partially supports the Proxy API.

Hello, Proxy!

The following piece of code creates a proxy that intercepts property access and returns for each property "p", the value "Hello, p":

var p = Proxy.create({
  get: function(proxy, name) {
    return 'Hello, '+ name;
  }
});

document.write(p.World); // should print 'Hello, World'

Try it in your browser.

The call Proxy.create(handler) returns a new proxy object. All property accesses performed on this proxy will be interpreted by calling the handler argument's get method. That is, the code p.foo is interpreted by the VM as if executing the code handler.get(p,'foo') instead, where handler is the first argument to Proxy.create. The Proxy.create function takes a second optional argument specifying the prototype of the proxy, which defaults to null.

Property access is not the only operation that can be intercepted by proxies. The following table lists the most important operations:

Operation Intercepted as
proxy[name] handler.get(proxy, name)
proxy[name] = val handler.set(proxy, name, val)
name in proxy handler.has(name)
delete proxy[name] handler.delete(name)
for (var name in proxy) {...} handler.iterate()
Object.keys(proxy) handler.keys()

Methods of the handler object that are invoked by manipulating the corresponding proxy are called traps.

The full proxy handler API additionally traps calls to new ES5 built-in methods such as Object.keys and Object.getOwnPropertyDescriptor. Fortunately it is not always necessary to implement the full API to build useful proxies, as demonstrated in the following section.

A simple profiler

Let's construct a simple kind of generic wrapper: a profiler that counts the number of times each of its properties was accessed:

function makeSimpleProfiler(target) {
  var forwarder = new ForwardingHandler(target);
  var count = Object.create(null);
  forwarder.get = function(rcvr, name) {
    count[name] = (count[name] || 0) + 1;
    return this.target[name];
  };
  return {
    proxy: Proxy.create(forwarder,
                        Object.getPrototypeOf(target)),
    get stats() { return count; }
  };
}

The function makeSimpleProfiler takes as its sole argument the object we want to monitor. It returns a tuple t such that t.proxy refers to the wrapper that will record profiling data, and t.stats can be used to retrieve the profiling information recorded thus far. We can use this abstraction as follows:

var subject = { foo: 42, bar: 24 };
var profiler = makeSimpleProfiler(subject);
runApp(profiler.proxy);
plotHistogram(profiler.stats);

Try it in your browser.

The function ForwardingHandler on line 2 of makeSimpleProfiler constructs a simple forwarding proxy that transparently delegates all operations performed on the proxy to the target object instead. Here is part of its definition:

function ForwardingHandler(obj) {
  this.target = obj;
}
ForwardingHandler.prototype = {
  has: function(name) { return name in this.target; },
  get: function(rcvr,name) { return this.target[name]; },
  set: function(rcvr,name,val) { this.target[name]=val;return true; },
  delete: function(name) { return delete this.target[name]; }
  enumerate: function() {
    var props = [];
    for (name in this.target) { props.push(name); };
    return props;
  },
  iterate: function() {
    var props = this.enumerate(), i = 0;
    return {
      next: function() {
        if (i === props.length) throw StopIteration;
        return props[i++];
      }
    };
  },
  keys: function() { return Object.keys(this.target); },
  ...
};
Proxy.wrap = function(obj) {
  return Proxy.create(new ForwardingHandler(obj),
                      Object.getPrototypeOf(obj));
}

A complete forwarding handler implementation is available here. This forwarding handler is also likely to become part of the standard so that you won't have to implement it yourself.

Emulating noSuchMethod

Using the Proxy API, it's fairly straightforward to emulate the functionality of SpiderMonkey/Rhino's __noSuchMethod__ hook in a browser that does not support it natively (of course, as long as Firefox is the only browser with proxies, this doesn't make much sense). All that is required is that the object eventually delegate to a special proxy object that will trap missing invocations:

function MyObject() {};
MyObject.prototype = Object.create(NoSuchMethodTrap);
MyObject.prototype.__noSuchMethod__ = function(methodName, args) {
  return 'Hello, '+ methodName;
};

new MyObject().foo() // returns 'Hello, foo'

Below is the code for the NoSuchMethodTrap root object. The get handler method short-circuits invocations of __noSuchMethod__. If it wouldn't do this, a missing method invocation on an object that didn't implement __noSuchMethod__ would recursively trigger the hook, leading to an infinite recursion.

var NoSuchMethodTrap = Proxy.create({
  get: function(rcvr, name) {
    if (name === '__noSuchMethod__') {
      throw new Error("receiver does not implement __noSuchMethod__ hook");
    } else {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        return this.__noSuchMethod__(name, args);
      }
    }
  }
});

Try it in your browser.

Remote objects

Proxies allow you to create virtual objects that represent remote or persisted objects. To demonstrate this, let's write a wrapper around an existing library for remote object communication in Javascript. Tyler Close's web_send library can be used to construct remote references to server-side objects. One can then "invoke" methods on this remote reference via HTTP POST requests. Unfortunately, remote references cannot be used as objects themselves. That is, to send a message to a remote reference ref, one writes:

Q.post(ref, 'foo', [a,b,c]);

It would be a lot more natural if one could instead treat ref as if it were an object, such that one could call its methods as ref.foo(a,b,c). Using the Proxy API, we can write a wrapper for remote web_send objects to translate property access into Q.post calls:

function Obj(ref) {
  return Proxy.create({
    get: function(rcvr, name) {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        return Q.post(ref, name, args);
      };
    }
  });
}

Now one can use the Obj function to write Obj(ref).foo(a,b,c).

Higher-order Messages

A higher-order message is a message that takes another message as its argument, as explained in this paper. Higher-order messages are similar to higher-order functions, but are sometimes more succinct to write. Using the Proxy API, it's straightforward to build higher-order messages. Consider a special object named _ that turns messages into functions:

var msg = _.foo(1,2)
msg.selector; // "foo"
msg.args; // [1,2]
msg(x); // x.foo(1,2)

Note that msg is a function of one argument, as if defined as function(r) { return r.foo(1,2); }. The following example is a direct translation from the aforementioned paper and shows how higher-order messages lead to shorter code:

var words = "higher order messages are fun and short".split(" ");
String.prototype.longerThan = function(i) { return this.length > i; };
// use HOM to filter and map based on messages rather than functions
document.write(words.filter(_.longerThan(4)).map(_.toUpperCase()));
// without HOM, this would be:
// words.filter(function (s) { return s.longerThan(4) })
//       .map(function (s) { return s.toUpperCase() })

Finally, here is the code for the _ object:

// turns messages into functions
var _ = Proxy.create({
  get: function(_, name) {
    return function() {
      var args = Array.prototype.slice.call(arguments);
      var f = function(rcvr) {
        return rcvr[name].apply(rcvr, args);
      };
      f.selector = name;
      f.args = args;
      return f;
    }
  }
});

Try it in your browser.

Emulating host objects

Proxies give Javascript programmers the ability to emulate much of the weirdness of host objects, such as the DOM. This enables library writers to wrap such host objects in order to tame them (i.e. for security sandboxing) or to fix them (to reduce the number of cross-browser incompatibilities). For a concrete example, Andreas Gal from Mozilla is currently experimenting with a Javascript-hosted DOM API using Proxies.

Function Proxies

All of the above examples use the Proxy API to create (non-callable) objects. However, what if you need to wrap a callable object, like a function? The Proxy API supports function proxies, which are created by calling fproxy = Proxy.createFunction(handler, call, construct). handler is the same kind of handler object passed to Proxy.create. call and construct are functions that are called when the function proxy is used in a function call or new expression, respectively:

Operation Intercepted as
fproxy(a,b,c) call(a,b,c)
new fproxy(a,b,c) construct(a,b,c)

Here is a simple example of function proxies in action:

var simpleHandler = {
  get: function(proxy, name) {
    // can intercept access to the 'prototype' of the function
    if (name === 'prototype') return Object.prototype;
    return 'Hello, '+ name;
  }
};
var fproxy = Proxy.createFunction(
  simpleHandler,
  function() { return arguments[0]; }, // call trap
  function() { return arguments[1]; }); // construct trap

fproxy(1,2); // 1
new fproxy(1,2); // 2
fproxy.prototype; // Object.prototype
fproxy.foo; // 'Hello, foo'

Try it in your browser.

Unlike Proxy.create, Proxy.createFunction does not take a prototype parameter. The prototype of a function proxy itself is always Function.prototype (that is, Object.getPrototypeOf(fproxy) === Function.prototype). Also, typeof fproxy returns "function", not "object". Note that, because function calling and function construction are trapped by different traps, you can use function proxies to reliably detect whether or not your function was called using the new keyword.

Function proxies also enable some other idioms that were previously hard to accomplish in pure JS. Thanks to Dave Herman for pointing these out:

First, function proxies make it simple to wrap any object as a callable object:

function makeCallable(target, call, construct) {
  return Proxy.createFunction(
    new ForwardingHandler(target),
    call,
    construct || call);
}

Second, function proxies also make it simple to create pseudo-classes whose instances are callable:

function Thing() {
  /* initialize state, etc */
  return makeCallable(this, function() {
    /* actions to perform when instance
       is called like a function */
  });
}

Experimenting with new semantics

For the language buffs: Proxies can be used to create an implementation of 'standard' Javascript objects and functions in Javascript itself. Once the semantics of Javascript objects is written down in Javascript itself, it becomes easy to make small changes to the language semantics, and to experiment with those changes. A partial implementation of the semantics of Javascript in Javascript itself can be seen in the trap defaults. As another example, David Bruant implemented Javascript Arrays in Javascript itself using Proxies. Bob Nystrom illustrates how Proxies could be used to augment Javascript objects with multiple inheritance.

Proxy Tips

Avoiding runaway recursion

Beware of implicit toString method calls on proxies. Be careful when manipulating the receiver argument in the get and set traps. The receiver is likely to be the proxy itself, so touching the proxy in one of these traps is likely to lead to infinite recursion. For example, printing the receiver for debugging will send a toString() message to it, which may lead to infinite recursion. For example, say we have a simple proxy handler that just forwards all operations to a target object. For debugging purposes, we'd like to know the initial receiver of a property access:

get: function(receiver, name) {
  print(receiver);
  return target[name];
}

If p is a proxy with the above get trap, then p.foo will go into an infinite loop: first the get trap is called with name="foo", which prints receiver (that is bound to p). This in turn calls p.toString(), which calls the get trap again, this time with name="toString". Again, receiver is bound to p, so printing it again will cause another toString() invocation, and so on.

Proxies as handlers

Proxy handlers can be proxies themselves. The Proxy Tracer below demonstrates how this pattern can be used to "funnel" all operations applied to a proxy handler through a single get trap.
Proxy Tracer/Probe

A tracer proxy that simply prints a description of all operations performed on itself. Useful for debugging, or for learning how operations are intercepted by proxy handlers.

Try it in your browser.

A probe proxy that, like the tracer above, logs all meta-level operations applied to it, but that is more configurable (the default logger prints the operations, but you can provide your own logger).

Try it in your browser.

More Examples

  • A generic object tracer [code]: simply logs all operations performed on an object via the tracer proxy.
  • A revocable reference [code]: a proxy that forwards all operations to a target object until revoked. After it is revoked, the proxy can no longer be used to affect the target object. Useful if an object only requires temporary access to another object.
  • A revocable reference with funneling [code]. Funneling uses a proxy as a handler so that all operations trap a single "get" trap.
  • A generic membrane [code]: a membrane transitively wraps all objects that enter or exit the membrane. When the membrane is revoked, the entire object graph of objects wrapped inside the membrane is disconnected from objects outside of the membrane. Membranes are the basis of security wrappers and can be used to isolate untrusted code.

Additional Resources on Harmony Proxies

  • The ECMAScript Harmony spec proposal.
  • The Mozilla Developer Network Proxy documentation.
  • A conformance test suite for the specification is available and can be run using a Javascript shell or in a browser.
  • Design background: first half of this Google Tech Talk and the following academic paper, presented at DLS 2010.
  • Brendan Eich, on his blog, concisely explains some of the rationale behind Proxies.
  • Sebastian Markbåge, on his blog, explains the "leaky this" problem: proxies are often used to wrap objects, but the wrapped object may "leak" outside of its wrapper in some cases.
  • Partial list of open issues in the Firefox 4 implementation of Proxies.
  • Frameworks making use of proxies:
    • dom.js, a self-hosted JavaScript implementation of a WebIDL-compliant HTML5 DOM.
    • mdv, model-driven views.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Generic License.