Skip to content
Tom Van Cutsem edited this page Aug 30, 2014 · 6 revisions

Harmony-reflect

An ES5 shim for the ES6 Proxy and Reflect 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 to be up-to-date with the latest ES6 spec.

Proxy

Proxy objects are a new feature of ECMAScript 6 that allow developers to write generic wrappers. As of august 2014, they are supported in Chrome (behind a flag, Firefox and node.js (using node --harmony). Chrome and node.js define a Proxy object that is not up-to-date with the latest ES6 draft. This library patches the Proxy object to follow the latest spec.

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.

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) and Reflect.deleteProperty (to delete a property) and Reflect.preventExtensions (to make an object non-extensible).

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

TODO

Avoid legacy __proto__

TODO

Clone this wiki locally