What does the third argument of Reflect.construct(target, argumentsList, newTarget) control, and what can it build that `new target(...)` cannot?
answer
- decides which prototype the instance gets
- also sets new.target in the body
- one constructor allocates, another shapes
- how extends Array really works
- Base.call(this) never did this
basics
~20 snewTarget 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 linesfunction 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); // truego deeper
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.
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.
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.
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