5 min read
ES6 introduced a new global object Reflect
that exposes low-level methods for object reflection, specifically Introspection - the ability to inspect, interact and manipulate object properties programmatically at runtime.
Like the global Object
, Reflect
is a static object so it cannot be instantiated with a constructor and the keyword new
nor can it invoked as a function. All Reflect
methods are also static.
Some introspection methods already exist in ES5 such as Object.keys()
and Object.getOwnPropertyNames
so one may ask why introduce the Reflect
object in ES6 if many of the same Object
methods are also present as Reflect
methods? There a number of reasons.
Reflect
standardizes and bundles all the methods one needs to work with objects in one single object. Object
is meant to be a base prototype not a repository of reflection methods so having a dedicated API namespce to expose reflection methods is preferable.
Reflect
methods have subtle differences relative to Object
methods. Most notably, Reflect
methods return boolean true (success) /false (failure) whereas Object
methods throw an exception if the method execution fails:
const obj = {};
try {
Object.defineProperty(obj, 'property', 'value');
} catch (e) {
// failure that needs to be handled
}
No try/catch blocks are required with Reflect
rather just a simple if
conditional that is easier and cleaner to handle than error exceptions:
if (Reflect.defineProperty(obj, 'property', 'value')) {
// success
} else {
// failure
}
Reflect
methods are first-class functions like Reflect.has(obj, prop)
compared to the ES5 equivalent (prop in obj)
.
Reflect
introduces methods that are not available with Object
such as the set()
, get()
and deleteProperty()
methods. As you can readily summize, set()
sets an object property, get()
gets an object property and deleteProperty()
deletes an object property:
const obj = {};
Reflect.set(obj, 'name', 'John Doe'));
console.log(obj.name); // 'John Doe'
Reflect.get(obj, 'name'));
console.log(obj.name); // 'John Doe'
To delete an object property in ES5 one uses the delete
operator:
delete obj.name;
Generally speaking, it is always cleaner and more consistent to call a method on an object than using an operator:
Reflect.deleteProperty(obj, 'name');
console.log(obj.name); // undefined
Further, the delete
operator cannot be grouped into a cohesive set of reflection methods where one would naturally expect to find it.
And since arrays are objects in JavaScript, we can do likewise with arrays as well:
const array = ['John Doe'];
Reflect.set(array, 1, 'Jane Doe'));
console.log(array[1]); // 'Jane Doe'
Reflect.get(array, 1));
console.log(array[1]); // 'Jane Doe'
Reflect.deleteProperty(array, 0));
console.log(array); // Array [, 'Jane Doe']
There are also Reflect
introspection methods that work with functions. Let's have a look at the Reflect.apply()
method that calls a function and the Reflect.construct()
method that instantiates a new object using a function instead of the new
operator.
Reflect.apply() passes three required parameters:
this
provided for the call to target
target
should be calledfunction volumeDimensions(length, width, height) {
this.length = length;
this.width = width;
this.height = height;
}
const obj = {};
const dimensions = [2, 5, 10];
Reflect.apply(volumeDimensions, obj, dimensions);
console.log(obj); // { length: 2, width: 5, height: 10 }
const volume = obj.length * obj.width * obj.height;
console.log(volume); // 100
In ES5, one would accomplish the same result this way:
Function.prototype.apply.call(volumeDimensions, obj, dimensions);
console.log(obj.length * obj.width * obj.height); // 100
The two implementations are basically equivalent but Reflect.apply()
is less verbose and easier to understand. Further, if the arguments list is null
or undefined
, Reflect.apply()
will throw an exception whereas Function.prototype.apply.call()
will call the function without any arguments.
Reflect.construct() passes two required parameters and one optional parameter:
target
should be calledtarget
is used.const target = class Person {
constructor(name, email) {
this.name = name;
this.email = email;
}
get contact() {
return { name: this.name, email: this.email };
}
};
const contactDetails = ['John Doe', 'johndoe@example.com'];
let person = Reflect.construct(target, contactDetails);
console.log(person.contact); // { name: 'John Doe', email: 'johndoe@example.com' }
One unique reason you may choose to use the Reflect.construct()
method in lieu of the new
operator is the third optional parameter that lets you target the newTarget
constructor's prototype to match the target
constructor's prototype:
const newTarget = class Employee {
get contact() {
return { employee: this.name, email: this.email };
}
};
person = Reflect.construct(target, contactDetails, newTarget);
console.log(person.contact); // { employee: 'John Doe', email: 'johndoe@example.com' }
In conclusion, the Reflect API gives you everything you need to perform object introspection capabilities all grouped together in the Reflect global object.
The Reflect
methods discussed above are used as "traps" when creating a ES6 proxy. Please refer to our companion blog post http://www.medium.com/@xoor/introduction_to_javscript_es6_proxies- for further discussion.
There are a few other Reflect
methods available that are not discussed in this introduction so please refer to the MDN web docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect for a complete reference.
And if you wish to compare the Object
and Reflect
methods, please refer to the MDN web docs https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods for a side-to-side comparison of their differences.