Skip to content
Tom Van Cutsem edited this page Jun 24, 2015 · 6 revisions

Harmony-reflect

An ES5 shim for the ES6 Reflect and Proxy objects

Why should I use this library?

This library does two things:

  • It defines an ES6-compliant Reflect global object that exports the Ecmascript 6 reflection API.
  • It patches the harmony-era (pre-ES6) Proxy object (as implemented in v8 and spidermonkey) to be up-to-date with the ES6 (aka ECMAScript 2015) spec.

Proxy

Proxy objects are a new feature of ECMAScript 6 that allow developers to write generic wrappers.

Since proxies are not yet widely supported on the client-side, this library is currently most useful when you're doing server-side development with node.js and want to start using ES6 proxies in node today. node.js (and the v8 engine on which it is based), when invoked using the --harmony_proxies flag, is still providing an older version of the Proxy object that is not up-to-date with the ES6 spec. This library patches that Proxy object to follow the latest ES6 spec.

To track browser-side compatibility of ES6 Proxies, see kangax's ES6 compatibility table.

Reflect

The Reflect object provides a number of utility functions, many of which appear to overlap with ES5 methods defined on the global Object. See the API docs for an overview. The Reflect shim should work in any ES5-compliant browser, and in node.js. It does not depend upon the availability of proxies.

Here are a number of reasons why the Reflect object is useful:

More useful return values

Many operations in Reflect are similar to ES5 operations defined on Object, such as Reflect.getOwnPropertyDescriptor and Reflect.defineProperty. However, whereas Object.defineProperty(obj, name, desc) will either return obj when the property was successfully defined, or throw a TypeError otherwise, Reflect.defineProperty(obj, name, desc) is specced to simply return a boolean that indicates whether or not the property was successfully defined. This allows you to refactor this code:

try {
  Object.defineProperty(obj, name, desc);
  // property defined successfully
} catch (e) {
  // possible failure (and might accidentally catch the wrong exception)
}

To this:

if (Reflect.defineProperty(obj, name, desc)) {
  // success
} else {
  // failure
}

Other methods that return such a boolean success status are Reflect.set (to update a property), Reflect.deleteProperty (to delete a property), Reflect.preventExtensions (to make an object non-extensible) and Reflect.setPrototypeOf (to update an object's prototype link).

First-class operations

In ES5, the way to detect whether an object obj defines or inherits a certain property name is to write (name in obj). Similarly, to delete a property, one uses delete obj[name]. While dedicated syntax is nice and short, it also means you must explicitly wrap these operations in functions when you want to pass the operation around as a first-class value.

With Reflect, these operations are readily defined as first-class functions: Reflect.has(obj, name) is the functional equivalent of (name in obj) and Reflect.deleteProperty(obj, name) is a function that does the same as delete obj[name].

More reliable function application

In ES5, when one wants to call a function f with a variable number of arguments packed as an array args and binding the this value to obj, one can write:

f.apply(obj, args)

However, f could be an object that intentionally or unintentionally defines its own apply method. When you really want to make sure that the built-in apply function is called, one typically writes:

Function.prototype.apply.call(f, obj, args)

Not only is this verbose, it quickly becomes hard to understand. With Reflect, you can now make a reliable function call in a shorter and easier to understand way:

Reflect.apply(f, obj, args)

Variable-argument constructors

Imagine you want to call a constructor function with a variable number of arguments. In ES6, thanks to the new spread syntax, it will be possible to write code like:

var obj = new F(...args)

In ES5, this is harder to write, because one can only use F.apply or F.call to call a function with a variable number of arguments, but there is no F.construct function to new the function with a variable number of arguments. With Reflect, one can now write, in ES5:

var obj = Reflect.construct(F, args)

Default forwarding behavior for Proxy traps

When using Proxy objects to wrap existing objects, it is very common to intercept an operation, do something, and then to "do the default thing", which is typically to apply the intercepted operation to the wrapped object. For example, say I want to simply log all property accesses to an object obj:

var loggedObj = new Proxy(obj, {
  get: function(target, name) {
    console.log("get", target, name);
    // now do the default thing
  }
});

The Reflect and Proxy APIs were designed in tandem, such that for each Proxy trap, there exists a corresponding method on Reflect that "does the default thing". Hence, whenever you find yourself wanting to "do the default" thing inside a Proxy handler, the correct thing to do is to always call the corresponding method in the Reflect object:

var loggedObj = new Proxy(obj, {
  get: function(target, name) {
    console.log("get", target, name);
    return Reflect.get(target, name);
  }
});

The return type of the Reflect methods is guaranteed to be compatible with the return type of the Proxy traps.

Control the this-binding of accessors

In ES5 it's fairly easy to do a generic property access or property update. For instance:

var name = ... // get property name as a string
obj[name] // generic property lookup
obj[name] = value // generic property update

The Reflect.get and Reflect.set methods allow you to do the same thing, but additionally accept as a last optional argument a receiver parameter that allows you to explicitly set the this-binding when the property that you get/set is an accessor:

var name = ... // get property name as a string
Reflect.get(obj, name, wrapper) // if obj[name] is an accessor, it gets run with `this === wrapper`
Reflect.set(obj, name, value, wrapper)

This is occasionally useful when you're wrapping obj and you want any self-sends within the accessor to get re-routed to your wrapper, e.g. if obj is defined as:

var obj = {
  get foo() { return this.bar(); },
  bar: function() { ... }
}

Calling Reflect.get(obj, "foo", wrapper) will cause the this.bar() call to get rerouted to wrapper.

Avoid legacy __proto__

On some browsers, __proto__ is defined as a special property that gives access to an object's prototype. ES5 standardized a new method Object.getPrototypeOf(obj) to query the prototype. Reflect.getPrototypeOf(obj) does exactly the same, except that Reflect also defines a corresponding Reflect.setPrototypeOf(obj, newProto) to set the object's prototype. This is the new ES6-compliant way of updating an object's prototype.