Custom instanceof
intermediateWalk the prototype chain by hand, then meet Symbol.hasInstance — and learn where instanceof lies to you.
Implement myNew(Constructor, ...args) in JavaScript: prototype linking with Object.create, this binding, and the return-override rule that explains constructor quirks.
Without using
new, implementmyNew(Constructor, ...args)that behaves identically — including the obscure rule about constructors thatreturnsomething.
new looks atomic; it's actually four spec steps, and this question checks whether you can name all four. It's the natural companion to custom bind (whose hardest edge case is new) and prototypal inheritance (whose machinery this operator drives).
The four steps:
Constructor.prototype.Constructor with this bound to that object.function myNew(Constructor, ...args) {
if (typeof Constructor !== "function") {
throw new TypeError(`${Constructor} is not a constructor`);
}
// Step 1: fresh object, prototype-linked to Constructor.prototype.
// Object.create does link-at-birth — no mutation, no __proto__.
const instance = Object.create(Constructor.prototype);
// Step 2: run the constructor with `this` = the new object
const result = Constructor.apply(instance, args);
// Steps 3–4: object return overrides; primitive return is discarded
const isObject =
result !== null && (typeof result === "object" || typeof result === "function");
return isObject ? result : instance;
}function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greet = function () {
return `Hi, I'm ${this.name}`;
};
const p = myNew(Person, "Ada", 36);
p.name; // "Ada"
p.greet(); // "Hi, I'm Ada" — prototype methods work (step 1)
p instanceof Person; // true — the chain is linked
// The return-override rule:
function ReturnsObject() { this.a = 1; return { b: 2 }; }
myNew(ReturnsObject); // { b: 2 } — object return WINS; this.a is lost
function ReturnsPrimitive() { this.a = 1; return 42; }
myNew(ReturnsPrimitive); // { a: 1 } — primitive return silently IGNOREDThat last pair is the discriminating test. Most candidates know steps 1–2; steps 3–4 are the "did you read the spec or a blog post" line — and they're not trivia: the return-override rule is why factory-style constructors and singleton patterns work with new at all, and why a stray return in a constructor produces one of JavaScript's most confusing bugs (half your fields vanish, no error anywhere).
Object.create(Constructor.prototype), not {} + mutation — setting __proto__ after creation deoptimizes in engines and reads as not knowing Object.create exists. Link at birth..apply(instance, args) — this is the one context where this is a brand-new object; connect it to the binding rules: new binding beats every other rule, including an explicit bind.!== null guard matters because typeof null === "object" (the famous bug).myNew(() => {}) should throw like real new does. Our version throws, but later and differently: arrows have no prototype, so Object.create(undefined) throws a TypeError about the prototype argument. Real new rejects at the [[Construct]] check. If asked to match exactly, check Constructor.prototype existence up front — and note that this heuristic can't distinguish arrows from methods ({ m() {} } also lacks prototype), which real new handles via an internal slot userland can't see. Naming that limit honestly is the senior answer.class constructors — myNew(SomeClass) throws: classes are only callable via [[Construct]] (Class constructor cannot be invoked without 'new'). Your apply is a plain call, so this fails — correctly demonstrating that classes are not just sugar over functions in every respect. Great volunteered detail.new.target — inside a real new, new.target is the constructor; inside myNew it's undefined, because only [[Construct]] sets it. This is exactly why bind's modern constructor-detection uses new.target — and why your polyfill can't fool it. Two honest gaps (this and classes) beat one pretend-perfect answer.instance is simply garbage-collected; no cleanup needed, worth one sentence.Reflect.construct(Constructor, args) — the real userland new: handles classes, sets new.target, even allows a different newTarget third argument. "My myNew is the teaching version; Reflect.construct is the production version" closes the question.const instance = {}; instance.__proto__ = C.prototype — works, slow, and signals 2014.typeof result === "object" without the null check.instanceof — the prototype link made in step 1 is exactly what instanceof walks; the two questions are one mechanism viewed from both ends.instanceof." — the standard pairing: custom instanceof checks the link your step 1 created.new Date() differ from Date()?" — many built-ins branch on new.target (or its legacy equivalent): Date() without new returns a string. Constructor-vs-call dual behavior is spec-sanctioned and your myNew can't reproduce it for built-ins — tie back to [[Construct]].new cost vs Object.create + init function?" — essentially nothing different in modern engines (hidden classes form either way); the real answer is API semantics, not performance.bind's constructor case interact?" — new (fn.bind(a))() ignores the bound this — because new binding wins; the full mechanics live in custom bind.this pitfalls, trivially composable, but no shared prototype methods without extra wiring and no instanceof. The trade-off discussion is prototypal inheritance's closing argument.Walk the prototype chain by hand, then meet Symbol.hasInstance — and learn where instanceof lies to you.
Implement bind including partial application and the new-operator edge case most candidates miss.
Copy enumerable own properties across sources, with getter evaluation and null-target errors.
Same trick as call but with an arguments array — plus the subtle differences that interviewers probe.