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])

The Internal Mechanics of Reflect.construct

When invoked, Reflect.construct() mirrors the internal [[Construct]] method triggered by the new keyword through three main steps:

  1. Object Allocation and Prototype Assignment: A new object is created in memory. By default, its internal prototype ([[Prototype]]) is set to target.prototype. If newTarget is provided, it is set to newTarget.prototype.
  2. Constructor Execution: The target function is executed with its this context bound to the newly allocated object, passing the elements of argumentsList as positional parameters.
  3. 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.