Click here to Skip to main content
15,886,110 members
Articles / Programming Languages / ECMAScript 6

JavaScript Reflection and Reflect API

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
8 Aug 2020CPOL8 min read 11K   1  
A walkthrough to the different methods of what JavaScript Reflect API provides to developers
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?

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:

JavaScript
/** 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 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:

JavaScript
/** 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 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.

JavaScript
/** 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.

JavaScript
/** 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 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:

JavaScript
/** 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 object
  • name - second argument is the name of the property that you want to be removed

Let us see a code sample below:

JavaScript
// /** 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 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:

JavaScript
/** 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 object
  • name - second argument object's property name
  • receiver - third argument is the receiver

Let us see a code sample below:

JavaScript
/** 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:

JavaScript
/** 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:

Image 1

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:

JavaScript
/** 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:

JavaScript
/** 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 object
  • newProto - second argument is the object's __proto__ property value

Let us see a code sample below:

JavaScript
/**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 object
  • name - second argument is the object's property name

Let us see a code sample below:

JavaScript
/** 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:

JavaScript
/** 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:

JavaScript
/** 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:

JavaScript
/** 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Philippines Philippines
Jin humbles himself as a C# programmer and a web developer, who loves backend and middleware development and still improving his skills at the front-end arena. He loves what he does, but far from perfect, here is a list of what he loves to do: read, write and code.
This is a Social Group

2 members

Comments and Discussions

 
-- There are no messages in this forum --