Reflect
API
The Reflect
object is a plain object (like Math
), not a function/constructor like the other built-in natives.
It holds static functions which correspond to various meta programming tasks that you can control. These functions correspond one-to-one with the handler methods (traps) that Proxies can define.
Some of the functions will look familiar as functions of the same names on Object
:
Reflect.getOwnPropertyDescriptor(..)
Reflect.defineProperty(..)
Reflect.getPrototypeOf(..)
Reflect.setPrototypeOf(..)
Reflect.preventExtensions(..)
Reflect.isExtensible(..)
These utilities in general behave the same as their Object.*
counterparts. However, one difference is that the Object.*
counterparts attempt to coerce their first argument (the target object) to an object if it’s not already one. The Reflect.*
methods simply throw an error in that case.
An object’s keys can be accessed/inspected using these utilities:
Reflect.ownKeys(..)
: Returns the list of all owned keys (not “inherited”), as returned by bothObject.getOwnPropertyNames(..)
andObject.getOwnPropertySymbols(..)
. See the “Property Enumeration Order” section for information about the order of keys.Reflect.enumerate(..)
: Returns an iterator that produces the set of all non-symbol keys (owned and “inherited”) that are enumerable (see the this & Object Prototypes title of this series). Essentially, this set of keys is the same as those processed by afor..in
loop. See the “Property Enumeration Order” section for information about the order of keys.Reflect.has(..)
: Essentially the same as thein
operator for checking if a property is on an object or its[[Prototype]]
chain. For example,Reflect.has(o,"foo")
essentially performs"foo" in o
.
Function calls and constructor invocations can be performed manually, separate of the normal syntax (e.g., (..)
and new
) using these utilities:
Reflect.apply(..)
: For example,Reflect.apply(foo,thisObj,[42,"bar"])
calls thefoo(..)
function withthisObj
as itsthis
, and passes in the42
and"bar"
arguments.Reflect.construct(..)
: For example,Reflect.construct(foo,[42,"bar"])
essentially callsnew foo(42,"bar")
.
Object property access, setting, and deletion can be performed manually using these utilities:
Reflect.get(..)
: For example,Reflect.get(o,"foo")
retrieveso.foo
.Reflect.set(..)
: For example,Reflect.set(o,"foo",42)
essentially performso.foo = 42
.Reflect.deleteProperty(..)
: For example,Reflect.deleteProperty(o,"foo")
essentially performsdelete o.foo
.
The meta programming capabilities of Reflect
give you programmatic equivalents to emulate various syntactic features, exposing previously hidden-only abstract operations. For example, you can use these capabilities to extend features and APIs for domain specific languages (DSLs).
Property Ordering
Prior to ES6, the order used to list an object’s keys/properties was implementation dependent and undefined by the specification. Generally, most engines have enumerated them in creation order, though developers have been strongly encouraged not to ever rely on this ordering.
As of ES6, the order for listing owned properties is now defined (ES6 specification, section 9.1.12) by the [[OwnPropertyKeys]]
algorithm, which produces all owned properties (strings or symbols), regardless of enumerability. This ordering is only guaranteed for Reflect.ownKeys(..)
(and by extension, Object.getOwnPropertyNames(..)
and Object.getOwnPropertySymbols(..)
).
The ordering is:
- First, enumerate any owned properties that are integer indexes, in ascending numeric order.
- Next, enumerate the rest of the owned string property names in creation order.
- Finally, enumerate owned symbol properties in creation order.
Consider:
var o = {};
o[Symbol("c")] = "yay";
o[2] = true;
o[1] = true;
o.b = "awesome";
o.a = "cool";
Reflect.ownKeys( o ); // [1,2,"b","a",Symbol(c)]
Object.getOwnPropertyNames( o ); // [1,2,"b","a"]
Object.getOwnPropertySymbols( o ); // [Symbol(c)]
On the other hand, the [[Enumerate]]
algorithm (ES6 specification, section 9.1.11) produces only enumerable properties, from the target object as well as its [[Prototype]]
chain. It is used by both Reflect.enumerate(..)
and for..in
. The observable ordering is implementation dependent and not controlled by the specification.
By contrast, Object.keys(..)
invokes the [[OwnPropertyKeys]]
algorithm to get a list of all owned keys. However, it filters out non-enumerable properties and then reorders the list to match legacy implementation-dependent behavior, specifically with JSON.stringify(..)
and for..in
. So, by extension the ordering also matches that of Reflect.enumerate(..)
.
In other words, all four mechanisms (Reflect.enumerate(..)
, Object.keys(..)
, for..in
, and JSON.stringify(..)
) will match with the same implementation-dependent ordering, though they technically get there in different ways.
Implementations are allowed to match these four to the ordering of [[OwnPropertyKeys]]
, but are not required to. Nevertheless, you will likely observe the following ordering behavior from them:
var o = { a: 1, b: 2 };
var p = Object.create( o );
p.c = 3;
p.d = 4;
for (var prop of Reflect.enumerate( p )) {
console.log( prop );
}
// c d a b
for (var prop in p) {
console.log( prop );
}
// c d a b
JSON.stringify( p );
// {"c":3,"d":4}
Object.keys( p );
// ["c","d"]
Boiling this all down: as of ES6, Reflect.ownKeys(..)
, Object.getOwnPropertyNames(..)
, and Object.getOwnPropertySymbols(..)
all have predictable and reliable ordering guaranteed by the specification. So it’s safe to build code that relies on this ordering.
Reflect.enumerate(..)
, Object.keys(..)
, and for..in
(as well as JSON.stringify(..)
by extension) continue to share an observable ordering with each other, as they always have. But that ordering will not necessarily be the same as that of Reflect.ownKeys(..)
. Care should still be taken in relying on their implementation-dependent ordering.