skip to content

What does the third argument of Reflect.construct(target, argumentsList, newTarget) control, and what can it build that `new target(...)` cannot?

level: seniorimportance: nice to knowfreq 24%

answer

  1. decides which prototype the instance gets
  2. also sets new.target in the body
  3. one constructor allocates, another shapes
  4. how extends Array really works
  5. Base.call(this) never did this

basics

~20 s

newTarget decides which constructor's .prototype the newly created object gets, and becomes the value of new.target inside the call. It lets one constructor allocate the object while a different one supplies the prototype, which is how an ordinary function can produce a real exotic Array or Error instance.

solid answer

~40 s

`Reflect.construct(target, argumentsList, newTarget)` runs `target` as a constructor but uses `newTarget` to decide the shape of the result: the new object's prototype comes from `newTarget.prototype`, and `new.target` inside the constructor body is `newTarget` rather than `target`. Omit it and it defaults to `target`, giving plain `new target(...args)` semantics. Splitting the two is something call syntax cannot express, and it matters for built-ins whose instances are exotic — arrays with a magic `length`, `Error` with an engine-populated stack, `Map` and `Set` with internal slots. `Reflect.construct(Array, [], MyArray)` produces a genuine exotic array object whose prototype is `MyArray.prototype`, so `Array.isArray` is true and `length` still updates. That is precisely the mechanism `class X extends Array` uses under the hood, and it is how down-compiled or non-class constructor functions reproduce it.

code

javascript · 14 lines
javascript
function MyArray() {
  return Reflect.construct(Array, [], MyArray);
}
MyArray.prototype = Object.create(Array.prototype);
MyArray.prototype.constructor = MyArray;
MyArray.prototype.last = function () { return this[this.length - 1]; };

const a = new MyArray();
a.push(1, 2, 3);

console.log(a.length);             // 3 — real exotic array length
console.log(Array.isArray(a));     // true
console.log(a.last());             // 3
console.log(a instanceof MyArray); // true

go deeper

for a junior

Know the signature and that the two-argument form is equivalent to new target(...args). The optional third argument decides which constructor's prototype the result gets.

for a middle

Explain new.target: which constructor the new expression named, why a base constructor reads new.target.prototype when it allocates, and how Reflect.construct lets you set that independently of the body being run.

for a senior

Connect it to exotic built-ins — arrays, Error, Map — and show why Base.call(this) can never produce them. Recognise the mechanism as what class extends Array compiles to, and forward it correctly from a construct trap.

for a principal

Judge when this level of indirection is justified at all. Manufacturing instances whose allocating constructor differs from their declared type complicates debugging, instanceof reasoning and tooling; require a concrete need such as genuine built-in subclassing before accepting it in shared code.

## The three parameters ```js Reflect.construct(target, argumentsList, newTarget); ``` - `target` — the constructor whose body actually runs. Must be a constructor: an arrow function, a generator function, a method shorthand, or `Math.max` will throw a `TypeError`. - `argumentsList` — an array-like of arguments. - `newTarget` — optional; defaults to `target`. Must also be a constructor. With two arguments it is simply `new target(...argumentsList)`. The third argument is the part that has no syntactic equivalent. ## What new.target is Every construct invocation carries a `new.target` value: the constructor that the `new` expression originally named. In a plain `new Foo()` it is `Foo`. In `class B extends A`, when `B`'s constructor calls `super()`, `A`'s body runs with `new.target === B` — that is how the base constructor knows which subclass is being built. This matters because the *base* is responsible for allocating the object, and it reads `new.target.prototype` to decide what to set as the new object's prototype. `Reflect.construct` exposes that plumbing directly: it lets you say "run `A`'s body, but pretend the `new` expression named `B`". ## Why that is not just a curiosity Some built-in constructors do not merely assign properties — they allocate objects with internal machinery ordinary code cannot reproduce: - `Array` instances are *exotic*: their `length` updates automatically and `Array.isArray` returns true for them. - `Error` instances get an engine-populated `stack`. - `Map`, `Set`, `Promise` and typed arrays have internal slots their methods require. You cannot obtain any of that by writing `const self = Object.create(MyArray.prototype)` — the object simply lacks the magic. You must let the real constructor allocate it, while still ending up with your own prototype: ```js function MyArray() { return Reflect.construct(Array, [], MyArray); } MyArray.prototype = Object.create(Array.prototype); MyArray.prototype.constructor = MyArray; MyArray.prototype.last = function () { return this[this.length - 1]; }; const a = new MyArray(); a.push(1, 2, 3); console.log(a.length); // 3 — real exotic array behaviour console.log(Array.isArray(a)); // true console.log(a.last()); // 3 console.log(a instanceof MyArray);// true ``` With `class MyArray extends Array {}` the language does this for you: `super()` runs `Array`'s allocation with `new.target` pointing at `MyArray`. `Reflect.construct` is the same mechanism, spelled out, for code that is not written as a class. ## The historical significance This is why ES5-style `function Sub() { Base.call(this); }` inheritance never worked for built-ins. `Base.call(this)` performs an ordinary *call*, not a *construct*: `Array.call(this)` ignores `this` entirely and returns a brand-new array, and `Error.call(this)` does the same. The subclass instance ends up with none of the base's exotic behaviour — a subclassed array whose `length` never moves is the canonical symptom. Down-compilers that target older syntax emit `Reflect.construct` in their inheritance helpers when the runtime provides it, precisely because it is the only correct workaround. ## The other, lesser use `Reflect.construct` also accepts an argument list as an array. Before spread syntax, invoking a constructor with a dynamic argument list required the notorious `new (Function.prototype.bind.apply(Ctor, [null, ...args]))()`. Today `new Ctor(...args)` handles that, so the argument-list convenience is no longer a reason to reach for it. `newTarget` is. ## Constraints and failure modes - Both `target` and `newTarget` must be constructors, otherwise `TypeError`. Class methods, arrow functions and most built-in *functions* (as opposed to constructors) are not. - `newTarget.prototype` is read at construction time, so reassigning `MyArray.prototype` after some instances exist affects only later ones. - If `newTarget.prototype` is not an object, the base's default prototype is used instead — a silent fallback that is easy to misread as your inheritance being ignored. - `Reflect.construct` mirrors the `construct` trap on a `Proxy`, which receives `(target, argumentsList, newTarget)` in the same order; forwarding with `Reflect.construct(target, args, newTarget)` preserves the subclass identity a trap would otherwise erase. ## When to reach for it Rarely, and deliberately: writing a factory or wrapper that must construct a caller-supplied class while preserving the caller's `new.target`, forwarding a `construct` trap, or building genuine subclass instances of a built-in from non-class code. In everyday code, `class ... extends` says the same thing far more legibly.

  • Why does the older `function Sub() { Array.call(this); }` pattern fail to subclass Array?
    Because `Array.call(this)` is a call, not a construct. Array ignores the `this` it is handed and returns a fresh exotic array, which the pattern discards. The instance therefore never gets the exotic length behaviour, and Array.isArray returns false for it. Reflect.construct with a newTarget is the only faithful fix.
  • What does Reflect.construct do if you pass a non-constructor as the target?
    It throws a TypeError. Being callable is not enough — arrow functions, generator functions, method shorthand and most built-in functions such as Math.max or Symbol have no [[Construct]] internal method. The same check applies to newTarget, which must also be a constructor.
  • How does a Proxy construct trap relate to this?
    The trap receives `(target, argumentsList, newTarget)` — the same three values in the same order. Forwarding with `Reflect.construct(target, args, newTarget)` preserves the subclass that the new expression named; forwarding with `new target(...args)` silently rewrites new.target to the target and gives every instance the wrong prototype.

saying these in an interview costs you the question

  • newTarget sets the this value inside the constructor
  • Reflect.construct is only a way to spread an argument array
  • Object.create(Sub.prototype) gives you a real array too
  • Any callable value can be passed as the target
  • new.target and this are the same thing

context