The behavior of a proxy is controlled by the handler, which can modify the original behavior of the target object in quite a few useful ways. The handler contains optional trap methods (e.g .get(), .set(), .apply()) called when the corresponding operation is performed on the proxy.

Interception

Let’s begin by taking a plain object and adding some interception middleware to it using the Proxy API. Remember, the first parameter passed to the constructor is the target (the object being proxied) and the second is the handler (the proxy itself). This is where we can add hooks for our getters, setters, or other behavior.

As we can see in practice, performing our property get or property set on the proxy object correctly resulted in a meta-level call to the corresponding trap on the handler. Handler operations include property reads, property assignment, and function application, all of which get forwarded to the corresponding trap.

The trap function can, if it chooses, implement an operation arbitrarily (e.g forwarding the operation to the target object). This is indeed what happens by default if a trap doesn’t get specified. E.g., here is a no-op forwarding proxy that does just this:

Identifying proxies

The identity of a proxy can be observed using the JavaScript equality operators (== and ===). As we know, when applied to two objects these operators compare object identities. The next example demonstrates this behavior. Comparing two distinct proxies returns false despite the underlying targets being the same. In a similar vein, the target object is different from any of its proxies:

Ideally, you shouldn’t be able to distinguish a proxy from a non-proxy object so that putting a proxy in place doesn’t really affect the outcome of your app. This is one reason the Proxy API doesn’t include a way to check if an object is a proxy nor provides traps for all operations on objects.

Use cases

As mentioned, Proxies have a wide array of use cases. Many of those above, such as access control and profiling fall under Generic wrappers: proxies that wrap other objects in the same address "space". Virtualization was also mentioned. Virtual objects are proxies that emulate other objects without those objects needing to be in the same address space. Examples include remote objects (that emulate objects in other spaces) and transparent futures (emulating results that are not yet computed).

Proxies as Handlers

A pretty common use case for proxy handlers is to perform validation or access control checks before performing an operation on a wrapped object. Only if the check is successful does the operation get forwarded. The below validation example demonstrates this:

More complex examples of this pattern might take into account all of the different operations proxy handlers can intercept. One could imagine an implementation having to duplicate the pattern of access checking and forwarding the operation in each trap.

This can be tricky to easily abstract, given each op may have to be forwarded differently. In a perfect scenario, if all operations could be uniformly funneled through just one trap, the handler would only need to perform the validation check once in the single trap. You could do this by implementing the proxy handler itself as a proxy. This is unfortunately out of scope for this article.

Object Extension

Another common use case for proxies is extending or redefining the semantics of operations on objects. You might for example want a handler to log operations, notify observers, throw exceptions instead of returning undefined, or redirect operations to different targets for storage. In these cases, using a proxy might lead to a very different outcome than using the target object.

Access control is another good use case for Proxies. Rather than passing a target object to a piece of untrusted code, one could pass its proxy wrapped in a sort of protective membrane. Once the app deems that the untrusted code has completed a particular task, it can revoke the reference which detaches the proxy from its target. The membrane would extend this detachment recursively to all objects reachable from the original target that was defined.

Using reflection with proxies

Reflect is a new built-in object that provides methods for interceptable JavaScript operations, very much useful for working with Proxies. In fact, Reflect methods are the same as those of proxy handlers.

Statically typed languages like Python or C# have long offered a reflection API, but JavaScript hasn’t really needed one being a dynamic language. One can argue ES5 already has quite a few reflection features, such as Array.isArray() or Object.getOwnPropertyDescriptor() which would be considered reflection in other languages. ES2015 introduces a Reflection API which will house future methods for this category, making them easier to reason about. This makes sense as Object is meant to be a base prototype rather than a bucket for reflection methods.

Using Reflect, we can improve on our earlier Superhero example for proper field interception on our get and set traps as follows:

Polyfilling Object.observe()

Although we're saying goodbye to Object.observe(), it's now possible to polyfill them using ES2015 Proxies. Simon Blackwell wrote a Proxy-based Object.observe() shim recently that's worth checking out. Erik Arvidsson also wrote a fairly spec complete version all the way back in 2012.

Browser support

ES2015 Proxies are supported in Chrome 49, Opera, Microsoft Edge and Firefox. Safari have had mixed public signals towards the feature but we remain optimistic. Reflect is in Chrome, Opera, and Firefox and is in-development for Microsoft Edge.

Google has released a limited polyfill for Proxy. This can only be used for generic wrappers, as it can only proxy properties known at the time a Proxy is created.