Purpose of Reflect

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.

Invoking a Function Through Reflect

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.

Accessing the Prototype

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.

Getting and Setting Properties

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

Exploring Properties Through Reflect

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.104.109