-
Notifications
You must be signed in to change notification settings - Fork 48
Home
An ES5 shim for the ES6 Proxy and Reflect objects
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 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.
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:
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).
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]
.
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)
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)
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.
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
.
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.