Implement the new Operator
Write myNew(Ctor, ...args): create object, set prototype, call constructor, return object unless the constructor returned an object.
- javascript
- prototypes
- polyfill
- interview
Most constructors never return. A few do, on purpose: a connection pool hands back a cached client, Error subclasses sometimes don’t, factories hide the instance. If your myNew always returns the object you allocated, you missed the last step of [[Construct]]. If you treat typeof result === 'object' as the test and forget that typeof null === 'object', you throw the instance away when the constructor returns null.
new is not “call the function.” It is allocate, link [[Prototype]], invoke with that object as this, then maybe discard the object because the constructor returned a different object (including a function).
Key takeaways
Object.create(Ctor.prototype)(with a fallback when.prototypeis not an object), thenCtor.apply(obj, args).- If the constructor returns an object or a function, that value is the result. Primitives,
undefined, andnullare ignored.- ES6
classconstructors throw when called.applycannot fakenewfor them.Reflect.constructcan.- Bound functions:
new boundFn()uses the original function’s.prototype.
Object.create, call, then maybe throw the instance away
The operator does four jobs (MDN new):
- Create a blank object.
- Set its
[[Prototype]]toCtor.prototypewhen that value is an object. Otherwise useObject.prototype. - Run
Ctorwiththisbound to the new object, forwarding arguments. - If the run returns a non-primitive, return that. Else return the allocated object.
function myNew(Ctor, ...args) {
if (typeof Ctor !== 'function') {
throw new TypeError(`${Ctor} is not a constructor`);
}
const proto = Ctor.prototype;
const obj = Object.create(
proto !== null && (typeof proto === 'object' || typeof proto === 'function')
? proto
: Object.prototype,
);
const result = Ctor.apply(obj, args);
if (result !== null && (typeof result === 'object' || typeof result === 'function')) {
return result;
}
return obj;
}
function Person(name) {
this.name = name;
}
Person.prototype.greet = function greet() {
return `hi ${this.name}`;
};
const p = myNew(Person, 'Ada');
p.greet(); // 'hi Ada'
Object.getPrototypeOf(p) === Person.prototype; // true
p instanceof Person; // true
The instanceof check works because we wired [[Prototype]] to Person.prototype. Same model as Prototypal Inheritance.
Return override
function Cache() {
this.fresh = true;
return { cached: true }; // object → wins
}
myNew(Cache).cached; // true
myNew(Cache) instanceof Cache; // false
function Nullish() {
this.ok = true;
return null; // ignored (`typeof null === 'object'` is the trap)
}
myNew(Nullish).ok; // true
function Boxed() {
return 42; // primitive → ignored
}
myNew(Boxed) instanceof Boxed; // true
function Factory() {
return function inner() { return 1; }; // functions are objects
}
typeof myNew(Factory); // 'function'
The null case is the one people fail. Spec Type for Null is not Object. User code must special-case result !== null before the typeof test. Functions must pass: typeof fn === 'function', and they are valid construct results.
.prototype that is not an object
function Broken() {
this.x = 1;
}
Broken.prototype = 3; // or null
const b = myNew(Broken);
Object.getPrototypeOf(b) === Object.prototype; // true
b instanceof Broken; // TypeError: prototype is not an object
new still succeeds. instanceof then throws because the right-hand .prototype is not an object. Handle that in Object.create’s argument, not by assuming every function has a usable .prototype.
What myNew cannot do
| Case | new Ctor() |
myNew(Ctor) |
|---|---|---|
| Ordinary function | constructs | constructs |
class C {} |
constructs | TypeError (class must be constructed, apply calls) |
| Arrow function | TypeError (no [[Construct]]) |
TypeError or a thrown body, no instance |
new.target inside Ctor |
the constructor | undefined (apply is a call) |
| Bound function | uses target’s .prototype |
uses boundFn.prototype which is undefined → Object.prototype |
The honest follow-up: Reflect.construct(Ctor, args) is the platform equivalent. It sets new.target, constructs classes, and unwraps bound functions.
function myNewReflect(Ctor, ...args) {
return Reflect.construct(Ctor, args);
}
Whiteboard myNew is still worth writing. It proves you know the four steps. Then you name the class / new.target gap.
Bound functions (footnote)
function C(x) {
this.x = x;
}
const Bound = C.bind(null, 1);
const a = new Bound();
a.x; // 1
a instanceof C; // true
a instanceof Bound; // true (engine unwraps [[BoundTargetFunction]])
Bound.prototype is undefined. A naive Object.create(Bound.prototype) becomes Object.create(Object.prototype) in the fallback, so a instanceof C fails. To match new, either call Reflect.construct or detect bound functions (Bound.name starting with "bound " is not a spec check; don’t). Say you would defer to Reflect.construct for this case.
this inside the constructor
Ctor.apply(obj, args) is the explicit-binding rule. Arrow constructors do not exist in any useful sense: arrows have no [[Construct]] and lexical this. If someone passes an arrow, throwing is correct.
Do not use new Ctor(...args) inside myNew. That cheats the prompt.
Procedure
- Reject non-functions.
proto = Ctor.prototype. If it is not an object, useObject.prototype.obj = Object.create(proto).result = Ctor.apply(obj, args).- Return
resultwhen it is a non-null object or a function. Otherwise returnobj. - Name classes,
new.target, and bound functions asReflect.constructterritory.
Interview out-loud answer
myNewallocates withObject.create(Ctor.prototype), calls the function with that object asthis, and returns the constructor’s return value when it is an object or a function.nulland primitives do not override, which is why thetypeoftest has to excludenull. Class constructors cannot beapply’d, so this helper is a model of ordinary functions. Bound functions andnew.targetneedReflect.construct. The prototype link is the same oneinstanceofwalks.
Related on this site
- Prototypal Inheritance:
[[Prototype]]vs.prototype - Implement instanceof: the check this wiring makes true
- Object.create Polyfill: the allocate step
- this Binding Rules:
newvsapply - call, apply, and bind: bound constructors
- Classes in JavaScript: why
classrejects a naked call
Further reading
Related guides
- Implement instanceofWalk [[Prototype]] until you hit the constructor’s prototype. Symbol.hasInstance, primitives, and the null-prototype object that is nobody’s instance.
- Array.filter PolyfillWrite filter that skips holes, respects thisArg, and never writes empty slots for rejected items. Contrast with map’s hole-preserving result.
- Array.find PolyfillWrite Array.prototype.find: first callback hit wins, holes are read as undefined, nothing matching returns undefined, and findIndex is the index twin.
- Array.flat PolyfillImplement flat(depth) with Infinity, holes, and non-array array-likes. Recursion vs stack, and why concat(spread) blows up on huge inputs.
- Array.forEach PolyfillImplement Array.prototype.forEach: visit present elements, skip holes, ignore the callback return, and honor thisArg without using native forEach.