Reflect in JavaScript Explained!
May 12th, 2021 — 11 min read — Gideon Idoko
Reflect is a built-in global object that was introduced in ES6 and dedicated to allow for effective reflection - the ability of a program to manipulate properties and methods of objects at runtime. The Reflect
object provides methods that have the ability to inspect, interact with, and manipulate object properties.
Unlike other global objects, Reflect is not a constructor nor a function, which means, it cannot be used with new
and cannot be invoked. All properties and methods of the Reflect
object are static like those of Math
and JSON
objects.
Why is Reflect Important?
- Reflect allows you to develop programs that are able to handle dynamic code.
- Prior to ES6 (ECMAScript 2015), only a few methods were available to help with reflection, Reflect wraps all the methods used to work with objects into a single object.
- The Reflect object makes it easy to interfere with the functionality of an existing object as some of its methods can mutate target objects.
- Reflect is a go-to place for various meta-operations on objects like getting and setting the prototype of objects.
Reflect Methods
I'll group the Reflect methods into two (2) categories: Mutating Reflect Methods and Non-Mutating Reflect Methods.
NB💡: The
target
object (to be manipulated), is always passed to Reflect Methods as the first argument. Arrays and objects can be thetarget
object since they are bothtypeof
object. ATypeError
exception is thrown iftarget
object is a non-object except in the case ofapply
orconstruct
methods where thetarget
is a function. This will be explained later.For an object,
propertyKey
is the property of that object while for an array,propertyKey
is the index of that array.
Mutating Reflect Methods
These consist of methods that modify the target
object, its value, or behavior. They include:
-
defineProperty()
Reflect.define(target, propertyKey, attribute)
The
Reflect.defineProperty()
method defines a new property (withpropertyKey
as key andattribute
as its descriptor) on thetarget
object. It returnstrue
if thepropertyKey
is successfully defined orfalse
if otherwise.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object Reflect.defineProperty(obj, 'three', { value: 3 }) ? console.log('property defined!') : console.log('could not define the propery'); // property defined! Reflect.defineProperty(arr, 2, { value: 'three' }); console.log(obj.three); // 3 console.log(arr[2]); // three
-
deleteProperty()
Reflect.deleteProperty(target, propertyKey)
The
Reflect.deleteProperty()
method deletespropertyKey
from thetarget
object. It works like the delete operator, but as a function. It returnstrue
ifpropertyKey
is successfully deleted orfalse
if otherwise.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object Reflect.deleteProperty(obj, 'two') ? console.log("property deleted!") : console.log("could not delete property"); // property deleted! Reflect.deleteProperty(arr, 1); console.log(obj.two); // undefined console.log(arr[1]); // undefined console.log(obj); // { one: 1 } console.log(arr); // [ one, empty ]
The length of an array remains the same after deletion because the
Reflect.deleteProperty
method, just like thedelete
operator, removes the value from the index and does not remove the index itself. -
apply()
Reflect.apply(target, thisArgument, argumentsList)
The
Reflect.apply()
method calls thetarget
function with arguments specified byargumentsList
, an array-like object.const obj = { one: 1, two: 2 }; // thisArguments const arr = ['one', 'two']; // argumentsList function func(a, b) { console.log(this); // { one: 1, two: 2 } console.log(a, b); // one two return a + ": " + this.one; // `this` references `obj` } console.log(Reflect.apply(func, obj, arr)); // one: 1 console.log(Reflect.apply(Math.ceil, undefined, [1.75])); // 2
The
Reflect.apply()
method is not really a mutating method as itstarget
is a function. It works like theFunction.prototype.apply()
.⚠ If the
target
is not callable orargumentsList
isnull
orundefined
,Reflect.apply()
would throw aTypeError
although,Function.prototype.apply()
would still call the function without any arguments. -
construct()
Reflect.construct(target, argumentsList[, newTarget])
The
Reflect.construct()
method is used to construct an object from thetarget
constructor function. It behaves like thenew
operator, but as a function. It is the same as callednew target(
...argumentsList
)
. TheargumentsList
(array-like object) specifies arguments and thenewTarget
(optional constructor function) specifies the constructor whose prototype should be used.function Func(a, b) { this.name = a; } // a = "func" function JavaScript(a, b) { this.name = b; } // b = "javascript" const args = ["func", "javascript"]; const obj1 = new Func(...args); const obj2 = Reflect.construct(Func, args, JavaScript); console.log(obj1.name); // "func" console.log(obj2.name); // "func" console.log(obj1 instanceof Func) // false console.log(obj2 instanceof Func) // false console.log(obj1 instanceof JavaScript) // true console.log(obj2 instanceof JavaScript) // true
⚠ If
target
ornewTarget
are not constructors,Reflect.construct()
would throw aTypeError
. -
set()
Reflect.set(target, propertyKey, value[, receiver])
The
Reflect.set()
method sets thepropertyKey
of thetarget
object withvalue
. If a property with the namepropertyKey
already exists, its value will be updated otherwise a new property will be created with the namepropertyKey
and valuevalue
.If the
propertyKey
already exists, and it has a setter function,receiver
(optional object) will be thethis
value inside the setter function.A boolean
true
is returned by theReflect.set()
method if thepropertyKey
was successfully set otherwisefalse
is returned.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object // add a `_one` accessor property to `obj` Object.defineProperty(obj, '_one', { get: function () { return this.one }, set: function (value) { this.one = ++value } }) Reflect.set(obj, 'three', 3) ? console.log("property successfully set") : console.log("could not set property"); // "property successfully set" Reflect.set(arr, 1, '2'); // set arr index of 1 to '2' const receiver = {}; Reflect.set(obj, '_one', 1, receiver); console.log(obj.three); // 3 console.log(arr[1]); // 2 console.log(obj); // { one: 1, two: 2, three: 3 } console.log(arr); // [ 'one', '2' ] console.log(receiver); // { one: 2 }
-
setPrototypeOf()
Reflect.setPrototypeOf(target, prototype)
The
Reflect.setPrototypeOf()
method sets the prototype of thetarget
object toprototype
(object or null). It returnstrue
if theprototype
was successfully set otherwisefalse
is returned.const obj1 = {}; // target object const obj2 = { two: 2 }; //target object const obj3 = { three: 3 } //target object Object.seal(obj3); //seal `obj3` to make it non-extensible console.log(Reflect.setPrototypeOf(obj1, { one: 1 })); // true console.log(Reflect.setPrototypeOf(obj2, null)); // true console.log(Reflect.setPrototypeOf(obj3, { three: 33})); // false console.log(obj1.one); // 1
⚠ If the
prototype
is neither an object nornull
,Reflect.setPrototypeOf()
would throw aTypeError
. Iftarget
object is non-extensible a booleanfalse
would be returned. -
preventExtensions()
Reflect.preventExtensions(target)
The
Reflect.preventExtensions()
method is used to prevent new properties from being added to thetarget
object i.e. thetarget
object will be made non-extensible.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object Reflect.isExtensible(obj) ? console.log("successfully made object non-extensible") : console.log("could not make object non-extensible"); // "successfully made object non-extensible" Reflect.preventExtensions(arr); arr[2] = "three"; // this will not happen console.log(arr); // logs [ 'one', 'two' ] and not [ 'one', 'two', 'three' ]
The
Reflect.preventExtensions()
method returnstrue
if thetarget
object is successfully made non-extensible otherwise it returnsfalse
. Once thetarget
object is made non-extensible, no new properties can be added to it.
Non-Mutating Reflect Methods
These methods do not modify the target
object, its value, or behavior. They include:
-
get()
Reflect.get(target, propertyKey[, receiver])
The
Reflect.get()
method is used to get a property on an object. It returns the value of thepropertyKey
property in thetarget
. Normally,this
references thetarget
object but thereceiver
(optional) argument is used asthis
, if provided and if the property with the same name aspropertyKey
is a getter function in thetarget
.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object //define a getter property Object.defineProperty(obj, 'three', { get() { return this.one + this.two; } }) console.log(Reflect.get(obj, 'one')); // 1 console.log(Reflect.get(obj, 'three')); // 3 console.log(Reflect.get(arr, 0)); // one console.log(Reflect.get(obj, 'three', { one: 11, two: 22 })); // 33
-
getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor(target, propertyKey)
The
Reflect.getOwnPropertyDescriptor()
method returns a property descriptor object that describes thepropertyKey
of thetarget
object , if found orundefined
if not.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object console.log(Reflect.getOwnPropertyDescriptor(obj, 'one')); // yields { value: 1, writable: true, enumerable: true, configurable: true } console.log(Reflect.getOwnPropertyDescriptor(arr, 1)); // yields { value: 'two', writable: true, enumerable: true, configurable: true }
The property descriptor object contains:
-
value: the value of
propertyKey
. -
writable: is
true
if thepropertyKey
value may be changed otherwisefalse
. -
enumerable: is true if the
propertyKey
surfaces during enumeration of the properties of thetarget
object otherwisefalse
. -
configurable: is
true
if the type ofpropertyKey
can be changed or if thepropertyKey
itself can be deleted from thetarget
object.
-
-
getPrototypeOf()
Reflect.getPrototypeOf(target)
The
Reflect.getPrototype
method returns the prototype of thetarget
object.class JavaScript {} const obj = {}; // target object const arr = ['one', 'two']; // target object const str = new String(); // target object const js = new JavaScript(); console.log(Reflect.getPrototypeOf(obj)); // Object { } console.log(Reflect.getPrototypeOf(arr)); // Array [] console.log(Reflect.getPrototypeOf(str)); // String { "" } console.log(Reflect.getPrototypeOf(js)); // JavaScript {}
-
has()
Reflect.has(target, propertyKey)
The
Reflect.has()
method can be used to check if apropertyKey
exists in thetarget
object. It works like thein
operator but as a function. It returntrue
ifpropertyKey
exists in thetarget
object, otherwise,false
.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object console.log(Reflect.has(obj, 'one')); // true console.log(Reflect.has(obj, 'three')); // false console.log(Reflect.has(arr, 1)); // true console.log(Reflect.has(arr, 2)); // false
-
isExtensible()
Reflect.isExtensible(target)
The
Reflect.isExtensible()
method is used to check if new properties can be added to thetarget
object.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object console.log(Reflect.isExtensible(obj)); // true console.log(Reflect.isExtensible(arr)); // true // prevent extensibility Object.seal(obj); Object.preventExtensions(arr); console.log(Reflect.isExtensible(obj)); // false console.log(Reflect.isExtensible(arr)); // false
-
ownsKey()
Reflect.ownsKey(target)
The
Reflect.ownsKey()
method returns an array of the keys of all own properties of thetarget
object. It includes both enumerable and non-enumerable properties.const obj = { one: 1, two: 2 }; // target object const arr = ['one', 'two']; // target object console.log(Reflect.ownKeys(obj)); // [ 'one', 'two' ] console.log(Reflect.ownKeys(arr)); // [ '0', '1', 'length' ]
Differences between Reflect
and Object
Methods
Some of Reflect
object static methods are the same as methods available on Object
but there are some differences between these methods. The differences are outline below:
Method Name | Reflect.[Method Name] | Object.[Method Name] |
---|---|---|
defineProperty() | Returns true if property was defined otherwise false . | Returns thetarget object if property was defined otherwise TypeError . |
getOwnPropertyDescriptor() | If target is not a primitive object, TypeError is returned. | If target is not a primitive object, it is coerced to one. |
getPrototypeOf() | Throws a TypeError for non-objects. | Throws a TypeError for non-objects in ES5, but coerces non-objects in ES2015. |
setPrototypeOf() | Returns true if prototype was successfully set, and false if it wasn't (including if prototype is non-extensible). Throws a TypeError if target is not an object, or if prototype is neither an Object nor null. | Returns the target object if prototype was set successfully. Throws a TypeError if prototype is neither Object nor null, or is non-extensible. |
preventExtenstions() | Returns true if the target object has been made non-extensible, and false if it has not. Throws a TypeError if the target object is not an object (a primitive). | Returns the target object if it has been made non-extensible. Throws a TypeError in ES5 if target is not an object (a primitive). In ES2015, treats the target as a non-extensible, ordinary object and returns the object itself. |
isExtensible() | Throws a TypeError if target is not an object (a primitive). | Throws a TypeError in ES5 if target is not an object (a primitive). In ES2015, it will be coerced into a non-extensible, ordinary object and will return false . |
Conclusion
The need for a single API for reflection gave rise to the creation of the Reflect
object. It has been seen that Reflect
methods perform the same functionality as some Object
static methods, some Function
prototype methods, and some operators
, however with the Reflect
object those functionalities can be performed by just calling its methods. The JavaScript language is still growing so more methods would definitely be added to the Reflect
object.
Thanks for reading :)