How Reflect.construct Mimics the new Operator in JavaScript
The Reflect.construct() method provides a programmatic
alternative to JavaScript’s new operator, allowing
developers to instantiate objects with dynamic argument lists and custom
prototype chains. This article explains the mechanics of
Reflect.construct(), how it replicates the standard
constructor invocation process, and the specific capabilities it
introduces beyond the standard new keyword.
Syntax and Basic Usage
The standard way to instantiate a class or constructor function is
with the new operator:
const instance = new TargetClass(arg1, arg2);Reflect.construct() achieves the exact same result
programmatically:
Reflect.construct(target, argumentsList [, newTarget])target: The constructor function or class to be invoked.argumentsList: An array-like object representing the arguments passed to the constructor.newTarget(optional): The constructor whose prototype should be used as the newly created object’s prototype.
The Internal
Mechanics of Reflect.construct
When invoked, Reflect.construct() mirrors the internal
[[Construct]] method triggered by the new
keyword through three main steps:
- Object Allocation and Prototype Assignment: A new
object is created in memory. By default, its internal prototype
(
[[Prototype]]) is set totarget.prototype. IfnewTargetis provided, it is set tonewTarget.prototype. - Constructor Execution: The
targetfunction is executed with itsthiscontext bound to the newly allocated object, passing the elements ofargumentsListas positional parameters. - Return Value Resolution: If the constructor explicitly returns a non-primitive object, that object is returned. Otherwise, the newly created instance is returned.
Functional Equivalence Example
Consider a simple class instantiation:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
// Using the new operator
const personA = new Person('Alice', 30);
// Using Reflect.construct
const personB = Reflect.construct(Person, ['Alice', 30]);
console.log(personA); // Person { name: 'Alice', age: 30 }
console.log(personB); // Person { name: 'Alice', age: 30 }In both cases, personA and personB are
identical instances of Person.
Key Advantages Over the
new Operator
While Reflect.construct() mimics the new
operator, it solves limitations inherent to static syntax:
1. Dynamic Arguments Without the Spread Operator
Prior to ES6 and the spread syntax (...), invoking a
constructor with a dynamic list of arguments required complex
workarounds using Object.create() and
Function.prototype.apply().
Reflect.construct() natively accepts arguments as an
array:
function createInstance(Constructor, argsArray) {
return Reflect.construct(Constructor, argsArray);
}2. Overriding
new.target and Customizing Prototypes
The third parameter, newTarget, allows a constructor to
be executed while adopting the prototype of a different class. This is
particularly useful for factory patterns, subclassing built-ins, and
metaprogramming:
class Base {
constructor(name) {
this.name = name;
}
}
class Extended {
greet() {
return `Hello, ${this.name}`;
}
}
// Invoke Base constructor, but assign Extended.prototype
const instance = Reflect.construct(Base, ['Bob'], Extended);
console.log(instance.name); // 'Bob' (set by Base constructor)
console.log(instance instanceof Base); // false
console.log(instance instanceof Extended); // true
console.log(instance.greet()); // 'Hello, Bob'In this pattern, the constructor logic of Base runs, but
new.target inside that constructor points to
Extended, resulting in an object inheriting from
Extended.prototype.