Chapter 7.4☕ 19 min

Constructor Functions & The 4-Step new Mechanism

new operator ke peeche kya hota hai? Sirf ek line likhte ho new User(), par V8 ke andar 4 step ka khel chalta hai.

01The 4 Steps of new: What V8 Does

The new operator is not magic — it follows a precise 4-step mechanism that V8 executes every time you write new Constructor():

Step 1: Create a brand new empty object.
Step 2: Set the object's prototype (__proto__) to the constructor's .prototype property.
Step 3: Execute the constructor function with this pointing to the new object.
Step 4: If the constructor returns an object, use that. Otherwise, return the new object.

This mechanism powers every object created with new — including ES6 class instances. Classes use the exact same 4 steps under the hood.

function User(name) {
  this.name = name;
  // Implicitly returns 'this'
}

// What happens: const sai = new User('Sai');
// Step 1: const obj = {};
// Step 2: obj.__proto__ = User.prototype;
// Step 3: User.call(obj, 'Sai'); // this = obj
// Step 4: return obj;

// Implementing new manually
function customNew(Constructor, ...args) {
  const obj = {};                          // Step 1
  Object.setPrototypeOf(obj, Constructor.prototype); // Step 2
  const result = Constructor.apply(obj, args);       // Step 3
  return result instanceof Object ? result : obj;    // Step 4
}

const ali = customNew(User, 'Ali');
console.log(ali.name); // 'Ali'
console.log(ali instanceof User); // true
02Forgetting new: The Global Pollution Trap

If you call a constructor function without the new keyword, this points to globalThis (which is window in browsers). This is one of JavaScript's most dangerous traps.

In sloppy mode: this.name = name creates a global variable on window! Silent bug — no error, no warning.

In strict mode: this is undefined, so this.name = name throws a TypeError immediately. Much safer!

function User(name) {
  // Safety check
  if (!new.target) {
    throw new Error('User must be called with new!');
    // Or auto-fix: return new User(name);
  }
  this.name = name;
}

// BAD: Calling without new (sloppy mode)
// const badUser = User('Sai'); // Pollutes window.name in sloppy!

// GOOD: Using new
const goodUser = new User('Sai'); // Safe!
console.log(goodUser.name); // 'Sai'
Engine detail: When V8 sees the new keyword, it generates a 'Construct' bytecode instead of a 'Call' bytecode. 'Construct' allocates a new JSObject, sets its prototype, and runs the function. Classes in ES6 use the exact same 'Construct' bytecode, which is why classes throw if called without new.
03Constructor Return Value Override

Constructors have a bizarre return value rule that can cause confusing bugs:

If a constructor returns a PRIMITIVE (string, number, boolean), the primitive is IGNORED. The new object is returned normally.

If a constructor returns an OBJECT (object, array, function), that object replaces the new object entirely!

function Normal() {
  this.value = 1;
  // Implicitly returns this
}
console.log(new Normal().value); // 1

function ReturnsPrimitive() {
  this.value = 2;
  return 42; // Primitive! Ignored!
}
console.log(new ReturnsPrimitive().value); // 2 (primitive ignored)

function ReturnsObject() {
  this.value = 3;
  return { hacked: true }; // Object! Overrides this!
}
const weird = new ReturnsObject();
console.log(weird.value);  // undefined (this was thrown away!)
console.log(weird.hacked); // true (returned object used)
Why does this exist? Historically, it allowed constructors to return arbitrary objects. It's rarely useful today but the spec preserves it for backward compatibility. The rule is simple: primitives ignored, objects replace. Just remember — never return objects from constructors unless you have a very specific reason.
04instanceof: The Prototype Chain Check

The instanceof operator checks if Constructor.prototype exists anywhere in the object's prototype chain. But it has important caveats:

It does NOT check if the object was actually created by the constructor. Any object with the right prototype in its chain passes the check.

It can be faked by changing __proto__ or Constructor.prototype.

Cross-realm issue: An array from an iframe fails instanceof Array of the parent window (different constructor).

function Animal() {}
const dog = new Animal();

console.log(dog instanceof Animal); // true
console.log(dog instanceof Object); // true (Animal.prototype is an object)

// Faking instanceof
const cat = {};
Object.setPrototypeOf(cat, Animal.prototype);
console.log(cat instanceof Animal); // true! But cat wasn't created by new Animal.

// Cross-realm issue
// const iframeArray = iframe.contentWindow.Array;
// console.log([] instanceof iframeArray); // false! Different Array constructor.

// Better type checking for primitives
console.log(typeof 'hello' === 'string'); // true
console.log('hello' instanceof String);   // false (primitive, not object)
For cross-realm safety, use Array.isArray() for arrays and typeof for primitives. For custom types, Symbol.hasInstance lets you customize instanceof behavior — but that's advanced territory. For most cases, instanceof works fine within a single realm.
05Constructor.prototype: The Shared Blueprint

Every function (except arrow functions) gets a .prototype property automatically when defined. This is the shared blueprint for all instances created via new.

The prototype object has a .constructor property that points back to the original function — creating a circular link.

Methods should be added to the prototype so all instances share one copy (saves memory). Properties specific to each instance belong inside the constructor.

function User(name) {
  // Own properties (unique per instance)
  this.name = name;
}

// Shared methods (one copy for all instances)
User.prototype.greet = function() {
  return 'Hi, I am ' + this.name;
};

const sai = new User('Sai');
const ali = new User('Ali');

console.log(sai.greet()); // 'Hi, I am Sai'
console.log(ali.greet()); // 'Hi, I am Ali'

// Both share the EXACT SAME greet function in memory
console.log(sai.greet === ali.greet); // true

// The constructor link
console.log(sai.constructor === User); // true
Memory Rule: Always put methods on the prototype and data properties in the constructor. If you put methods in the constructor (this.greet = function(){}), every instance gets its own copy — wastes memory. Prototype methods are shared by all instances.

Lo kar liya — Key Points:

  • ✅ The new operator does 4 things: creates object, sets __proto__, binds this, returns object (unless constructor returns an object)
  • ✅ Calling a constructor without new pollutes the global object in sloppy mode; use new.target to guard
  • ✅ If a constructor returns an object, that object replaces the new object; primitives are ignored
  • instanceof checks the prototype chain, not the actual constructor; it can be faked and fails across realms
  • Constructor.prototype is the shared blueprint for all instances; put methods there to save memory
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase