5 min read

faye-cornish-Uq3gTiPlqRo-unsplash

Introduction to Javascript ES6 Reflect API


Introduction to Javscript ES6 Reflect API

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:

  • target: Target function to call
  • thisArgument: Value of this provided for the call to target
  • argumentsList: Array-like object specifying the arguments with which target should be called
function 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: Target function to call
  • argumentsList: Array-like object specifying the arguments with which target should be called
  • newTarget: (Optional) Constructor whose prototype should be used. If not present, target 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.