JavaScript Reflection and Reflect API





1.00/5 (2 votes)
A walkthrough to the different methods of what JavaScript Reflect API provides to developers
Introduction
When it comes to reflection within the context of computer programming, it's defined as the ability to examine, introspect, and modify its own structure and behavior at runtime (the definition came from the Wikipedia page). Moreover, it is commonly known for meta-programming. Therefore, you can manipulate variables, properties, and methods of objects at runtime. With JavaScript language, it is possible to do a reflection. However, back in the old days, its limited and reflection methods weren’t straight-forward to many developers. But, today, that’s no longer the case, the Reflect
object provides better meaningful methods to help developers to easily do reflection programming. Therefore, we are going to tackle and see what the Reflect
object can offer us, especially its static
methods. OK, then let’s get started.
Table of Contents
- What is Reflect API?
- What are the various methods Reflect object provides?
- Reflect.apply(target, receiver, args)
- Reflect.construct(target, args, prototype)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target,name)
- Reflect.set(target,name,value)
- Reflect.get(target,name,receiver)
- Reflect.getOwnPropertyDescriptor(target,name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, newProto)
- Reflect.has(target,name)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.ownKeys(target)
- Summary
- History
What is Reflect API?
Here are the things to remember about the Reflect
API.
-
It uses
Reflect
which is a global & static object, thus, you can’t create an instance of it. Likewise, all of its methods arestatic
. -
It provides a runtime-level of inspecting and manipulating the properties of objects also known as meta-programming. Moreover, prior to ES6, the JavaScript language does provide object reflection API but these weren’t really organized and it throws an exception when it failed. Thus, today,
Reflect
API with the help ofReflect
object improves the way we do meta/reflection programming.
What are the Various Methods that Reflect Object Provides?
Reflect.apply(target, receiver, args)
This method calls the target function to be invoked with arguments specified. In other words, if you want to invoke a certain function without really directly invoking it but by using this method to invoke a target function.
This method takes three arguments:
target
- first argument which represents the target functionreceiver
- second argument which represents the value ofthis
inside the target functionargs
- third argument which represents the arguments of the target function in an array object
See the example below:
/** start of Reflect.apply() */
//let's define a function
function getSum(num1, num2) {
return `${this.value}${num1 + num2}`;
}
//let's try to invoke the function using Reflect.apply()
const returnedValueOfFunc = Reflect.apply(getSum, { value:'Sum of 1 and 2 is '}, [ 1, 2]);
console.log(returnedValueOfFunc); //output: Sum of 1 and 2 is 3
/** end of Reflect.apply() */
Reflect.construct(target,args,prototype)
This method is used to invoke a function as a constructor. In other words, this method returns a new instance created by the target constructor.
This method takes three arguments:
target
- the first argument which represents the target constructorargs
- the second argument which represents the arguments of the target constructor. This argument is optional.prototype
- the third argument which represents another constructor whose prototype will be used as the prototype of the target constructor. This argument is optional.
See the example below:
/** start of Reflect.constructor */
//let's define a constructor function
function Customer(title,firstName, lastName) {
this.title = title;
this.firstName = firstName;
this.lastName = lastName;
this.showFullName = function () {
return `${this.title}. ${lastName}, ${firstName} is from the ${this.country}`;
}
}
//let's define another constructor set the prototype to add a new property.
function Employee() { }
Employee.prototype.country = 'Philippines';
const myCustomer = Reflect.construct(Customer, ['Dr','Jin Vincent', 'Necesario'],Employee);
console.log(myCustomer.showFullName()); //output: Dr. Necesario,
//Jin Vincent is from the Philippines
/** end of Reflect.constructor */
Reflect.defineProperty(target, name, desc)
Probably, you can guess that this method is used to define a new property or update an existing property on an object. If that's what you are thinking, you guessed it right.
This method takes three arguments:
target
- first argument is the object that is used to define or modify a propertyname
- second argument is the name of the property that is to be defined or modifieddesc
- third argument is the descriptor for the property that is defined or modified
Moreover, it has an equivalent method which is Object.defineProperty()
. Now that we are aware of this, you might be thinking: "what's the difference?", I'll answer that in the next section. After that, we will get into code samples.
What's the difference between Reflect.defineProperty() and Object.defineProperty()?
Basically, these methods do the identical thing but the main difference is the value these methods return. Now the difference is that the Reflect.defineProperty()
method returns a Boolean
, while the Object.defineProperty()
returns the modified object. Moreover, if the method Object.defineProperty()
fails, then it throws an exception while Reflect.defineProperty()
method returns false
as a result.
What are the data properties and accessor properties?
Before getting into the code sample, let us try to understand the third argument of the method Reflect.defineProperty()
. In any object-oriented language, every object property is either a data property or an accessor property.
Basically, data property has a value that may be readable or not or writable or not, while accessor property has a getter-setter pair of functions to set and retrieve the property value.
Using Reflect.define() and defining a data property descriptor
Before we dive into the code, let's first see the descriptor object properties:
value
- This is the value associated with the property. It isundefined
by default.writable
- If it is set totrue
, the property value can be changed with the use of the assignment operator. It isfalse
by default.configurable
- If it is set totrue
, then the property attributes can be changed. It isfalse
by default.enumerable
- If it is set totrue
, then the property will show up in thefor ...in
loop andObject.keys()
method. It isfalse
by default.
Let us see a code sample for defining the property as writable, configurable, and enumerable.
/** start of Reflect.defineProperty */
const book = {};
//let's define a property that is writable, configurable and enumerable
Reflect.defineProperty(book, "title", {
value: "JavaScript For Kids",
writable: true,
configurable: true,
enumerable:true
});
//let's check the book object
console.log(book); //output: {title: "JavaScript For Kids"}
//let's check the title of the book
console.log(book.title); //output: JavaScript For Kids
//let's change the value of the Book property,
//this is possible because writable is set to true
book.title = "Beginning Node.js";
//let's check the title of the book
console.log(book.title); //output: Beginning Node.js
//let's check if we can enumerate the title property
for (const key in book) {
console.log(key); //output: title
}
/** end of Reflect.defineProperty */
Another example where defining the property as non-writable, non-configurable, and non-enumerable.
/** start of Reflect.defineProperty */
const laptop = {};
//let's define a property that isn't writable, configurable and enumerable
Reflect.defineProperty(laptop, "brand", {
value: "IBM",
writable: false,
configurable: false,
enumerable: false
});
//let's check the laptop object
console.log(laptop); //output: {brand: "IBM"}
//let's check the brand of the laptop
console.log(laptop.brand); //output: IBM
//let's change the value of the brand property,
//this is not possible because writable is set to false
laptop.brand = "DELL";
//let's check the brand of the laptop
console.log(laptop.brand); //output: IBM
//let's check if we can enumerate the brand property
for (const key in laptop) {
console.log(key); //output: n/a
}
/** end of Reflect.defineProperty */
Using Reflect.define() and defining an accessor property descriptor
Again, before diving into code samples, let us see the accessor property descriptor properties:
get
- A function that returns the property valueset
- A function that sets the property valueconfigurable
- If it is set totrue
, the property descriptor can be changed. It isfalse
by defaultenumerable
- if it is set totrue
, the property shows up infor..in
loop and theObject.keys()
method. It isfalse
by default.
Let us see a code sample below:
/** start of accessor property */
/** start of Reflect.defineProperty */
const laundryShop = {
__defaultName__: "Queens Care Laundry Shop"
}
Reflect.defineProperty(laundryShop, "businessName", {
get: function () {
return this.__defaultName__;
},
set: function (value){
this.__defaultName__ = value;
},
configurable: true,
enumerable: true
});
console.log(laundryShop); //output: {__defaultName__: "Queens Care Laundry Shop"}
console.log(laundryShop.businessName); //output: Queens Care Laundry Shop
laundryShop.businessName = "Laundry Shop";
console.log(laundryShop.businessName); //output: Laundry Shop
/** end of accessor property */
/** end of Reflect.defineProperty */
Reflect.deleteProperty(target,name)
The name of the method itself describes what it does. It basically removes the property of an object.
This method takes two arguments:
target
- first argument is the target/reference objectname
- second argument is the name of the property that you want to be removed
Let us see a code sample below:
// /** start of Reflect.deleteProperty */
let car = {
model: "Toyota Hilux",
yearModel: 2020
};
//let us see the object before removing the model property.
console.log(car); //output: {model: "Toyota Hilux", yearModel: 2020}
Reflect.deleteProperty(car, "model");
//let use the object after the removal of the model property.
console.log(car); //output: { yearModel: 2020 }
/** end of Reflect.deleteProperty */
Reflect.set(target, name, value)
This method is used to set the value of an object's property.
This method takes three arguments:
target
- first argument is the target/reference objectname
- second argument is the name of the object's propertyvalue
- third argument is the value of the property
Let us see a code sample below:
/** Start of Reflect.set */
const computer2 = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
console.log(computer2);
//output: {processor: "Intel", brand: "Dell", operatingSystem: "windows 7"}
Reflect.set(computer2, "processor", "AMD");
console.log(computer2);
//output: {processor: "AMD", brand: "Dell", operatingSystem: "windows 7"}
// /** end of Reflect.set */
Reflect.get(target, name, receiver)
Obviously, this method is the exact opposite of Reflect.set()
. This method is used to retrieve the value of an object's property.
This method takes three arguments:
target
- first argument is the target/reference objectname
- second argument object's property namereceiver
- third argument is the receiver
Let us see a code sample below:
/** Start of Reflect.get */
var computer1 = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
console.log(computer1);
Reflect.get(computer1, "processor");
console.log(computer1.processor);
/** end of Reflect.get */
Note: If the property is an accessor property, then we can provide the optional third argument which will be the value of this
inside the get
function.
Let us see a code sample below:
/** start of Reflect.get with 3rd argument */
const dinoComputer = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
Reflect.defineProperty(dinoComputer, "computerDetails", {
get: function() {
return new String().concat(`*********Computer Details********\r\n`,
`****Processor: ${this.processor}***********\r\n`,
`****Brand: ${this.brand}*****************\r\n`,
`****Operating System: ${this.operatingSystem}*\r\n`);
}
});
console.log(dinoComputer);
let oldComputer = Reflect.get(dinoComputer, "computerDetails",
{ processor: "AMD K62",
brand: "Clone",
operatingSystem: "Windows XP" });
console.log(oldComputer);
/** end of Reflect.get with 3rd argument */
Output:
Reflect.getOwnPropertyDescriptor(target,name)
This method is used to retrieve the descriptor of an object's property. Kinda easy to implement.
This method takes two arguments:
target
- first argument is the target/reference objectname
- second argument is the object's property name
Let us see a code sample below:
/** start of Reflect.getOwnPropertyDescriptor */
const myDog = {
yourPet: true,
name: "Bruno"
}
const descriptor = Reflect.getOwnPropertyDescriptor(myDog, "name");
console.log(descriptor.value); //output: Bruno
console.log(descriptor.writable); //output: true
console.log(descriptor.enumerable);//output: true
console.log(descriptor.configurable); //output: true
/** end of Reflect.getOwnPropertyDescriptor */
Reflect.getPrototypeOf(target)
This method is used to retrieve internal prototype of an object that is, the value of the internal property of an object. This method only has one argument which is the target/reference object.
One thing to note, it is the same as the Object.getPrototypeOf()
method.
Let us see a code sample below:
/** start of Reflect.getPrototypeOf*/
const product = {
__proto__: {
category: {
id: "1",
name: "Electronics",
description: "Electronic devices"
}
}
}
const myCategoryResult = Reflect.getPrototypeOf(product);
console.log(myCategoryResult.category);
//output: { id: "1", name: "Electronics", description: "Electronic devices" }
/** end of Reflect.getPrototypeOf */
Reflect.setPrototypeOf(target, newProto)
This method is used to set the internal prototype (__proto__
) property's value of an object.
This method takes two arguments:
target
- first argument is the target/reference objectnewProto
- second argument is the object's__proto__
property value
Let us see a code sample below:
/**start of Reflect.setPrototypeOf */
const anime = {
popularAnimeShow: "Voltes V"
}
Reflect.setPrototypeOf(anime, {
fullName: "Super Electromagnetic Machine Voltes V"
});
console.log(anime.__proto__.fullName);
//output: Super Electromagnetic Machine Voltes V
/** end of Reflect.setPrototypeOf */
Reflect.has(target,name)
This method is used to check if a property exists in an object. It returns true
if the property exists, otherwise false
.
This method takes two arguments:
target
- first argument is the target/reference objectname
- second argument is the object's property name
Let us see a code sample below:
/** start of Reflect.has */
const band = {
name: "EHeads",
songs: ['Ang Huling El Bimbo', 'With A Smile']
}
console.log(Reflect.has(band, "name")); //output: true
console.log(Reflect.has(band, "songs")); //output: true
console.log(Reflect.has(band, "producer")); //output: false
/** end of Reflect.has */
Reflect.isExtensible(target)
This method is used to check if an object is extensible or not. In other words, if we can add new properties to an object. This method only has one argument which is the target/reference object.
Let us see a code sample below:
/** start of Reflect.isExtensible */
const problem = {
problemIs: "I'm in love with a college girl"
}
console.log(Reflect.isExtensible(problem)); //output: true
Reflect.preventExtensions(problem); //let's prevent this object to be extended.
//This is the same as Object.preventExtensions(problem);
console.log(Reflect.isExtensible(problem)); //output: false
/** end of Reflect.isExtensible */
/** start of Reflect.preventExtensions */
A good thing to know, we can also mark an object as non-extensible via the following methods:
Object.preventExtension()
Object.freeze()
Object.seal()
Reflect.preventExtensions(target)
This method is used to mark an object as non-extensible. It returns a Boolean
, indicating whether it was successful or not. This method only has one argument which is the target/reference object.
Let us see a code sample below:
/** start of Reflect.preventExtensions */
const song = {
title: "Magasin",
band: "EHeads"
}
console.log(Reflect.isExtensible(song)); //output: true
Reflect.preventExtensions(song);
//This is the same as Object.preventExtensions(song);
console.log(Reflect.isExtensible(song)); //output: false
/** end of Reflect.preventExtensions */
Reflect.ownKeys(target)
This method returns an array of keys properties of an object. However, it ignores the inherited properties (__proto__
). This method only has one argument which is the target/reference object.
Let us see a code sample below:
/** start of Reflect.ownKeys */
const currency = {
name: "USD",
symbol: "$",
globalCurrency: true,
__proto__: {
country: "USA"
}
}
const keys = Reflect.ownKeys(currency);
console.log(keys); //output: ["name", "symbol", "globalCurrency"]
console.log(keys.length);//output: 3
/** Output:
* name
symbol
globalCurrency
*/
keys.forEach(element => {
console.log(element);
});
/** end of Reflect.ownKeys */
Summary
In this post, we have learned what is the JavaScript reflection API using the Reflect
object. Not only that, we have tackled most of the methods that Reflect
object has to offer and we have seen how to implement it in different ways in specific scenarios. Overall, this post has introduced the JavaScript Reflect
API.
I hope you have enjoyed this article, as I have enjoyed writing it. Stay tuned for more. Until next time, happy programming!
History
- 8th August, 2020: Initial version