How would you implement JavaScript's `new` operator yourself as a function `myNew(Fn, ...args)`, and what can that hand-rolled version not reproduce?
answer
- Object.create does creation and linking together
- apply supplies the receiver
- the returned value can win
- apply is an ordinary call underneath
- the real primitive already exists
basics
~20 sCreate an object with Object.create(Fn.prototype), invoke Fn.apply on it with the arguments, and hand back that object unless the call produced an object of its own. The imitation still cannot set new.target or construct class constructors.
solid answer
~50 sThe implementation mirrors the operator's steps. `const obj = Object.create(Fn.prototype)` covers creation and the prototype link in one call; `const result = Fn.apply(obj, args)` runs the body with `this` bound to the new object; then you return `result` when it is an object and `obj` otherwise. A careful version also falls back to `Object.prototype` when `Fn.prototype` is not an object, matching the real operator. What it cannot do is more interesting than what it can. Inside `Fn`, `new.target` is `undefined`, because `apply` performs an ordinary call — so any guard in the constructor either throws or recurses. A `class` constructor refuses to be called at all and throws a `TypeError`. And built-ins like `Array` or `Map` ignore the receiver you pass and return their own exotic objects. `Reflect.construct` is the real primitive; a hand-rolled version is an interview exercise, not a shim.
code
javascript · 24 linesfunction myNew(Fn, ...args) {
if (typeof Fn !== 'function') throw new TypeError('not a function');
const proto = (Fn.prototype !== null && typeof Fn.prototype === 'object')
? Fn.prototype
: Object.prototype;
const obj = Object.create(proto);
const result = Fn.apply(obj, args);
const isObject =
(result !== null && typeof result === 'object') || typeof result === 'function';
return isObject ? result : obj;
}
function Person(name) { this.name = name; }
Person.prototype.greet = function () { return 'Hi, ' + this.name; };
const p = myNew(Person, 'Ada');
console.log(p.greet()); // Hi, Ada
console.log(Object.getPrototypeOf(p) === Person.prototype); // true
class Point {}
try { myNew(Point); } catch (e) { console.log(e.constructor.name); } // TypeErrorgo deeper
Recall the two building blocks the exercise needs: Object.create makes an object with a chosen prototype, and apply calls a function with a chosen receiver and an argument list.
Write the four steps without hesitation and explain each one, including why the created object is returned when the body returns nothing and why linking beats copying the prototype's properties.
Volunteer the limits before you are asked — no new.target, class constructors refuse an ordinary call, exotic built-ins ignore the receiver you pass — and name Reflect.construct as what you would actually ship.
Take a position on run-time construction as a design tool: when a registry of constructors is the right abstraction, what it costs in traceability and bundling, and when a plain factory map is the simpler answer.
## Why this is asked Writing `new` by hand forces you to name each step instead of trusting the keyword. The exercise is short, and the interesting part is usually the follow-up: which parts of the operator can a plain function actually reproduce, and which are engine-level behaviour no library code can imitate? ## A working implementation ```js function myNew(Fn, ...args) { if (typeof Fn !== 'function') { throw new TypeError(`${String(Fn)} is not a function`); } const proto = (Fn.prototype !== null && typeof Fn.prototype === 'object') ? Fn.prototype : Object.prototype; const obj = Object.create(proto); // steps 1 + 2 const result = Fn.apply(obj, args); // step 3 const returnedObject = (result !== null && typeof result === 'object') || typeof result === 'function'; return returnedObject ? result : obj; // step 4 } function Person(name) { this.name = name; } Person.prototype.greet = function () { return `Hi, ${this.name}`; }; const p = myNew(Person, 'Ada'); p.greet(); // 'Hi, Ada' Object.getPrototypeOf(p) === Person.prototype; // true ``` ## Line by line **The prototype step.** `Object.create(proto)` allocates a fresh ordinary object whose internal prototype is exactly `proto` — creation and linking in one move. The classic wrong answers here are `Object.create(Fn)`, which links the instance to the *function* rather than to its prototype object, and `Object.assign({}, Fn.prototype)`, which copies enumerable own properties instead of establishing delegation and therefore breaks as soon as the prototype gains a method later. The `proto` computation reproduces a real detail of the operator: when `Fn.prototype` holds a primitive or `null`, construction uses the intrinsic `Object.prototype` instead of failing. **The call step.** `Fn.apply(obj, args)` runs the body with the new object as the receiver, so assignments to `this` become own properties of `obj`. `Reflect.apply(Fn, obj, args)` is equivalent and is safer against a constructor that shadowed `apply` on itself. **The result step.** The operator's result is the created object unless the call produced an object; the `typeof result === 'function'` clause matters because functions are objects too and would otherwise be dropped. ## What it cannot reproduce **`new.target` is missing.** `apply` performs an ordinary call, so inside `Fn` the meta-property is `undefined`. Any constructor that guards on it misbehaves under `myNew`: a throwing guard rejects a construction that looks legitimate, and a self-correcting guard quietly ignores your carefully prepared object and constructs a second one. ```js function Dog(name) { if (!new.target) throw new TypeError('Dog must be called with new'); this.name = name; } myNew(Dog, 'Rex'); // TypeError — the guard cannot tell this apart from a plain call ``` **Class constructors refuse.** A `class` constructor throws a `TypeError` when invoked without construction, so `Fn.apply(obj, args)` fails before the body runs. There is no way around this from library code — the restriction exists precisely so that a class can only be built the intended way. **Exotic built-ins ignore your object.** Constructors such as `Array`, `Map`, `Set`, `Date` and `Error` allocate their own specially-shaped objects. Calling them with a receiver you prepared does not turn that receiver into an array or a map; `Array.apply(obj, [1, 2, 3])` returns a genuine array and leaves `obj` untouched, so `myNew(Array, 1, 2, 3)` gives you something that is not the object you linked. **Derived classes.** Even setting the class restriction aside, a subclass constructor's object is produced by its base, with the originally-invoked constructor carried along — a chain a receiver-passing imitation has no way to drive. ## The real primitive `Reflect.construct(Fn, argsArray)` invokes the actual construct behaviour, works on classes and built-ins, and takes an optional third argument that supplies the value seen as `new.target`. If you genuinely need to construct a function whose identity is only known at run time, that is the tool; `myNew` is an explanatory exercise. ## How to answer well Write the four lines confidently, mention the non-object-prototype fallback as a detail you know is there, and then volunteer the limits — no `new.target`, no classes, no exotic built-ins — before being asked. Naming `Reflect.construct` as the thing you would actually use closes the loop and shows you know the difference between explaining a mechanism and shipping a shim.
- Why does `Object.create(Fn.prototype)` beat `Object.assign({}, Fn.prototype)` here?`Object.create` establishes delegation: the new object's internal prototype *is* `Fn.prototype`, so later additions to the prototype are visible on the instance and methods are shared rather than duplicated. `Object.assign` copies enumerable own properties once, misses non-enumerable and inherited ones, and freezes the instance's view at copy time. Only the first reproduces what the operator does.
- Your `myNew` is called with a constructor that guards on `new.target`. What happens?The guard sees `undefined`, because `apply` performs an ordinary call. A throwing guard rejects the construction outright; a self-correcting guard returns a freshly constructed object, which then wins as the result and silently discards the object `myNew` prepared. Either way the imitation is observably different from the operator.
- When would you reach for `Reflect.construct` in real code?When the constructor is chosen at run time and must be built properly — a registry mapping names to constructors, a wrapper that forwards a variable-length argument list, or code that must construct classes and built-ins. It runs the genuine construct behaviour and accepts an optional third argument to supply the `new.target` value, which no `apply`-based imitation can offer.
saying these in an interview costs you the question
- Uses Object.create(Fn) instead of Object.create(Fn.prototype)
- Calls Fn(...args) without binding the new object as the receiver
- Claims the hand-rolled version works on class constructors
- Assumes new.target is set inside a function invoked via apply
- Copies the prototype's properties onto the instance instead of linking