In this article, we will explore JavaScript’s Reflect API and see the usage of its different methods. Furthermore, most of the examples are made for a programmer who’s somewhat intermediate with the JavaScript language to easily grasp the concepts.
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 are static
.
-
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 of Reflect
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 function receiver
- second argument which represents the value of this
inside the target function args
- third argument which represents the arguments of the target function in an array object
See the example below:
function getSum(num1, num2) {
return `${this.value}${num1 + num2}`;
}
const returnedValueOfFunc = Reflect.apply(getSum, { value:'Sum of 1 and 2 is '}, [ 1, 2]);
console.log(returnedValueOfFunc);
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 constructor args
- 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:
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}`;
}
}
function Employee() { }
Employee.prototype.country = 'Philippines';
const myCustomer = Reflect.construct(Customer, ['Dr','Jin Vincent', 'Necesario'],Employee);
console.log(myCustomer.showFullName());
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 property name
- second argument is the name of the property that is to be defined or modified desc
- 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 is undefined
by default. writable
- If it is set to true
, the property value can be changed with the use of the assignment operator. It is false
by default. configurable
- If it is set to true
, then the property attributes can be changed. It is false
by default. enumerable
- If it is set to true
, then the property will show up in the for ...in
loop and Object.keys()
method. It is false
by default.
Let us see a code sample for defining the property as writable, configurable, and enumerable.
const book = {};
Reflect.defineProperty(book, "title", {
value: "JavaScript For Kids",
writable: true,
configurable: true,
enumerable:true
});
console.log(book);
console.log(book.title);
book.title = "Beginning Node.js";
console.log(book.title);
for (const key in book) {
console.log(key);
}
Another example where defining the property as non-writable, non-configurable, and non-enumerable.
const laptop = {};
Reflect.defineProperty(laptop, "brand", {
value: "IBM",
writable: false,
configurable: false,
enumerable: false
});
console.log(laptop);
console.log(laptop.brand);
laptop.brand = "DELL";
console.log(laptop.brand);
for (const key in laptop) {
console.log(key);
}
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 value set
- A function that sets the property value configurable
- If it is set to true
, the property descriptor can be changed. It is false
by default enumerable
- if it is set to true
, the property shows up in for..in
loop and the Object.keys()
method. It is false
by default.
Let us see a code sample below:
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);
console.log(laundryShop.businessName);
laundryShop.businessName = "Laundry Shop";
console.log(laundryShop.businessName);
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 object name
- second argument is the name of the property that you want to be removed
Let us see a code sample below:
let car = {
model: "Toyota Hilux",
yearModel: 2020
};
console.log(car);
Reflect.deleteProperty(car, "model");
console.log(car);
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 object name
- second argument is the name of the object's property value
- third argument is the value of the property
Let us see a code sample below:
const computer2 = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
console.log(computer2);
Reflect.set(computer2, "processor", "AMD");
console.log(computer2);
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 object name
- second argument object's property name receiver
- third argument is the receiver
Let us see a code sample below:
var computer1 = {
processor: "Intel",
brand: "Dell",
operatingSystem: "windows 7"
};
console.log(computer1);
Reflect.get(computer1, "processor");
console.log(computer1.processor);
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:
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);
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 object name
- second argument is the object's property name
Let us see a code sample below:
const myDog = {
yourPet: true,
name: "Bruno"
}
const descriptor = Reflect.getOwnPropertyDescriptor(myDog, "name");
console.log(descriptor.value);
console.log(descriptor.writable);
console.log(descriptor.enumerable);
console.log(descriptor.configurable);
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:
const product = {
__proto__: {
category: {
id: "1",
name: "Electronics",
description: "Electronic devices"
}
}
}
const myCategoryResult = Reflect.getPrototypeOf(product);
console.log(myCategoryResult.category);
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 object newProto
- second argument is the object's __proto__
property value
Let us see a code sample below:
const anime = {
popularAnimeShow: "Voltes V"
}
Reflect.setPrototypeOf(anime, {
fullName: "Super Electromagnetic Machine Voltes V"
});
console.log(anime.__proto__.fullName);
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 object name
- second argument is the object's property name
Let us see a code sample below:
const band = {
name: "EHeads",
songs: ['Ang Huling El Bimbo', 'With A Smile']
}
console.log(Reflect.has(band, "name"));
console.log(Reflect.has(band, "songs"));
console.log(Reflect.has(band, "producer"));
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:
const problem = {
problemIs: "I'm in love with a college girl"
}
console.log(Reflect.isExtensible(problem));
Reflect.preventExtensions(problem);
console.log(Reflect.isExtensible(problem));
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:
const song = {
title: "Magasin",
band: "EHeads"
}
console.log(Reflect.isExtensible(song));
Reflect.preventExtensions(song);
console.log(Reflect.isExtensible(song));
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:
const currency = {
name: "USD",
symbol: "$",
globalCurrency: true,
__proto__: {
country: "USA"
}
}
const keys = Reflect.ownKeys(currency);
console.log(keys);
console.log(keys.length);
keys.forEach(element => {
console.log(element);
});
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!
- 8th August, 2020: Initial version