We’ve used a few methods of Reflect so far in this book. It’s time to step back and take a fresh look at this class. Reflect has two main purposes:
It’s a go-to place for various meta-operations on objects. For example, Reflect provides methods to get and set the prototype of an object and to check whether a property exists in an object, just to mention a couple.
The Proxy class, which we’ll see soon, by default routes its methods to methods of Reflect. Then when using a proxy we can override only select operations and conveniently leave the rest to the default implementations.
There are about two dozen methods in Reflect; let’s sample a few interesting and often used methods of this class.
In traditional JavaScript there were three different ways to invoke a function: using (), call(), or apply(). Suppose we have a function named greet:
| const greet = function(msg, name) { |
| const pleasantry = typeof(this) === 'string' ? this : 'have a nice day'; |
| console.log(`${msg} ${name}, ${pleasantry}`); |
| }; |
We can invoke the greet() function, for example, with arguments ’Howdy’ and ’Jane’, in one of the following ways:
greet(’Howdy’, ’Jane’); is the most common way to invoke, with no implicit context object attached to the call. Each of the arguments provided was able to bind, based on the position, to the corresponding parameters of the function.
greet.call(’how are you?’, ’Howdy’, ’Jane’);, where the first argument binds to this—the context object—and the remaining arguments bind to the parameters of the function.
greet.apply(’how are you?’, [’Howdy’, ’Jane’]);, where the first argument binds to this and each element in the second array argument binds to the parameters of the function.
Although these methods are still available in modern JavaScript, if a context object needs to be passed in, Reflect’s apply() function is now the preferred alternative to using call() or apply() directly on the function. Here’s the rewrite of the last invocation to greet(), using Reflect’s apply():
| Reflect.apply(greet, 'how are you?', ['Howdy', 'Jane']); |
It may appear to be redundant at first, but Reflect’s apply() function is quite useful when altering behavior of method calls, as you’ll see in Intercepting Function Calls Using Proxy.
JavaScript now has a elegant way to get and change the prototype of an object. Let’s access the prototype of an instance of Date to learn about the new methods of Reflect.
| const today = new Date(); |
| console.log(Reflect.getPrototypeOf(today)); |
| |
| const myPrototype = {}; |
| Reflect.setPrototypeOf(today, myPrototype); |
| |
| console.log(Reflect.getPrototypeOf(today)); |
We obtained the prototype of the today instance using the getPrototypeOf() method—this returns the Date class’s prototype, as we see in the output shown next. Then, using the setPrototypeOf() we modify the prototype of the today instance. We then verify that the change took effect by fetching the prototype of the instance yet again.
| Date {} |
| {} |
We saw the power and purpose of prototypes in Understanding Prototypal Inheritance. Modifying the prototype of an object is a risky business—it alters the inheritance hierarchy of instances—so use it judiciously and sparingly.
In Dynamic Access we explored ways to dynamically access properties. Reflect provides alternatives to both get and set properties. Let’s revisit the example where we used [] to access properties. Here’s the Person class we created earlier, repeated for convenience:
| class Person { |
| constructor(age) { |
| this.age = age; |
| } |
| |
| play() { console.log(`The ${this.age} year old is playing`); } |
| |
| get years() { return this.age; } |
| } |
To access the age property, for example, on an instance sam of Person, we can perform sam.age. However, if we don’t know the name of the property at code writing time, we can pass the property name as string to Reflect’s get() method. To set a value for the property, we can use the set() method. Let’s see how:
| const sam = new Person(2); |
| |
| const propertyName = 'age'; |
| |
| Reflect.set(sam, propertyName, 3); |
| console.log(Reflect.get(sam, propertyName)); |
The call to set() changes the initial value of age from 2 to 3. The call to get() returns the current value of the property.
It may appear that there’s no real benefit to using Reflect’s get() or set() to access a property dynamically instead of using []. If at all, the code is more verbose in comparison. That’s a reasonable assessment, but get() and set() will make more sense when we use the methods in the context of Proxy later in this chapter.
In Invoking a Function Through Reflect, we used Reflect’s apply() to invoke a stand-alone function. We can use apply() to call a method of a class as well. Let’s call the play() method using Reflect.apply():
| Reflect.apply(sam.play, sam, []); |
| Reflect.apply(Person.prototype.play, sam, []); |
For the first argument, to get a reference to the play() method, we can use either the instance or the class’s prototype as reference. The second argument has to be the instance on which we like to invoke the method—that is, the context this object. The third argument is the array of arguments—an empty array since play does not have any parameters. The output from these two calls is
| The 3 year old is playing |
| The 3 year old is playing |
Reflect has methods to iterate over the keys of an object and to check whether a property exists in an object. Here’s an example to get an array of all the keys in an object and to check if the object has a property named age:
| console.log(Reflect.ownKeys(sam)); |
| console.log(Reflect.has(sam, 'age')); |
The ownKeys() method of Reflect takes an instance as a parameter and returns an array of all the key names—properties, fields, and methods. The has() method will return true if the instance passed for the first parameter contains a property named in the second parameter; it will return false otherwise.
Here’s the output from this snippet of code:
| [ 'age' ] |
| true |
In addition to providing a way to access various metadata of an object, Reflect serves as a conduit for default implementation of methods in Proxy. Metaprogramming, especially method synthesis, relies on Proxy. Let’s dive into that topic next and learn how to dynamically introduce methods into classes.