JavaScript: Why Reflect APIs?
In my last post on Metaprogramming we had a high level look at the JavaScript(ES6) Proxy. In this post, we are going to discuss about JavaScript Reflect APIs, another inclusion in ES6.
First thing first, What is Reflect?
Just before we go to understand what is Reflect in JavaScript, let us brush up a bit on the Concept of Metaprogramming itself. As described by Wikipedia,
Metaprogramming is a programming technique in which computer programs have the ability to treat other programs as their data. It means that a program can be designed to read, generate, analyze or transform other programs, and even modify itself while running.
So basically, it is the Program that deals with the Meta Data of another program and able to do lot of useful things.
Reflection is a branch of Metaprogramming. Reflection has three sub branches:
- Introspection: Code is able to inspect itself.
- Self-Modification: As the name suggests, code is able to modify itself.
- Intercession: Acting behalf of somebody else. This can be achieve by wrapping, trapping, intercepting.
Reflect is all about Reflection through Introspection. It is used to discover very low level information about your code.
Why Reflect APIs?
As part of this post, I do not mean to explain each of the Reflect APIs deeply. At the end of the post, I have captured few References which do that job very well. This post is mostly about my learning on the existence of those APIs. Basically the answer of the question, Why to Bother about Reflect APIs?
In ES6, the Reflect is a new Global Object(like Math) that provides a number of utility functions, many of which appear to overlap with ES5 methods defined on the global Object. All these functions are Introspection functions where you could query some internal details about the program at the run time.
Hold On a Sec! Isn't Introspection tools already exist in JavaScript? How about
Object.keys
: Method is used to return an array whose elements are strings corresponding to the enumerable properties found directly upon an object.?Object.getOwnPropertyNames
: method returns an array of all properties?- Many more...
So why do we need a new API when these could just exist already or can be added to Object itself?
Image Courtesy: https://giphy.com/search/confused
Confused? Let us try to get the answer:
All in One Namespace: JavaScript already had been supporting APIs for object reflection but, these APIs were not organized under a namespace. Since ES6 those are under Reflect.
Unlike most global objects, Reflect is not a constructor. You cannot use it with a new operator or invoke the Reflect object as a function. All properties and methods of Reflect are static like Math Object.
Simplicity in use: The Introspection methods on Object throw exception when they fail to complete the operation. This is an added burden to the consumer(programmer) to handle that exception in code.
I would always prefer to handle it as a
boolean
(true | false) than theexception handling
.See the difference below:
Example 1: Object.defineProperty
try { Object.defineProperty(obj, name, desc); // property defined successfully } catch (e) { // possible failure and need to do something about it }
Example 2: With Reflect API
if (Reflect.defineProperty(obj, name, desc)) { // success } else { // failure (and far better) }
Feeling of First-Class Operations: In ES5, the way you would find the existence of a property for an Object as,
(prop in obj)
. If you need to use it multiple times in your code, you must explicitly wrap these operations in functions when you want to pass the operation around as a first-class value.In ES6, we already got those as part of Reflect API as first-class function, like,
Reflect.has(obj, prop)
is the functional equivalent of(prop in obj)
.Reliability in function apply: In ES5 the most reliable way of calling a function
func
with a variable number of arguments as an arrayarr
and binding thethis
value toobj
:Function.prototype.apply.call(func, obj, arr);
Of course you could short-hand it as,
func.apply(obj, arr);
This is less reliable because,
func
could be an object that would have defined its ownapply
.In ES6 we have more reliable and elegant way of solving this:
Reflect.apply(func, obj, arr);
Proxy Trap Forwarding: This is an interesting aspect. If you are new to
Proxy
, I would strongly encourage you to read about Proxy to understand it better.When we use Proxy object to wrap the existing object, we commonly intercept an operation. We try to do something special and then once that is done, we want to continue doing the normal thing. Let us understand this with the following example:
const employee = { firstName: 'Tapas', lastName: 'Adhikary' }; let logHandler = { get: function(target, fieldName) { console.log("Log: ", target[fieldName]); return Reflect.get(target, fieldName); } }; let func = () => { let p = new Proxy(employee, logHandler); p.firstName; p.lastName; }; func();
In the above example I am using a
get
trap and logging the property values. Once logged, I am using theReflect
API to get the value as usual.Output:
Log: Tapas Log: Adhikary
Note: There is a Reflect API exist for each of the Proxy Traps.
Quiz Time
While defining Reflect above I mentioned that, it is all about Reflection through Introspection. There are two other categories of Reflection, Self-Modification and Intercession.
The Question is, if Reflect is Introspection, what is JavaScript's implementation of Intercession?
The answer is very much here!
References
Here are some cool references to understand Reflect APIs in more depth:
- https://www.keithcirkel.co.uk/metaprogramming-in-es6-part-2-reflect/
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect
Conclusion
Reflect APIs are cool. Embrace them if not done already. Your code will not look like Object hacks anymore rather, it will point to nice APIs serving great purposes. Hope this gives you some new insight.
Please hit the "Likes" if you have enjoyed reading it!
Credits
The stunning image of Reflection in the Cover of the post is taken from, https://www.pexels.com/