Before ES6 classes existed, constructor functions were the way to create multiple objects with the same shape. Think of a constructor function like a blueprint — we write it once and stamp out as many objects as we need.
A constructor function is just a regular function, but we follow two conventions:
- The name starts with a capital letter (like
Person,Car,User). - We call it with the
newkeyword.
function Person(name, age) {
this.name = name;
this.age = age;
this.greet = function () {
console.log(`Hi, I'm ${this.name}`);
};
}
const manish = new Person("Manish", 25);
manish.greet(); // "Hi, I'm Manish"
The 4 steps new performs
When we write new Person("Manish", 25), JavaScript does four things behind the scenes:
- Creates an empty object —
{} - Sets the prototype — links the new object’s
[[Prototype]]toPerson.prototype - Calls the function with
thisbound to the new object - Returns the object — if the function doesn’t explicitly return an object, JavaScript returns the newly created one
Here’s what that looks like if we did it manually:
// What "new Person('Manish', 25)" does under the hood:
const obj = {}; // Step 1
Object.setPrototypeOf(obj, Person.prototype); // Step 2
Person.call(obj, "Manish", 25); // Step 3
// Step 4: return obj (automatically)
Understanding these 4 steps is crucial — it explains why this works inside constructors and how prototype inheritance is set up.
What happens if we forget new?
If we call a constructor function without new, this will point to the global object (or be undefined in strict mode). That means properties get attached to the wrong place:
const oops = Person("Manish", 25); // forgot new!
console.log(oops); // undefined (no return value)
console.log(window.name); // "Manish" — leaked to global!
This is a common bug. ES6 classes fix this by throwing an error if we forget new.
Return value from constructors
If a constructor explicitly returns a primitive (string, number, etc.), JavaScript ignores it and returns the this object anyway. But if it returns an object, that object replaces this:
function Weird() {
this.name = "original";
return { name: "replaced" }; // returning an object overrides this
}
const w = new Weird();
console.log(w.name); // "replaced"
function Normal() {
this.name = "original";
return 42; // primitive return is ignored
}
const n = new Normal();
console.log(n.name); // "original"
instanceof
instanceof checks whether an object was created by a particular constructor (more precisely, whether the constructor’s .prototype exists in the object’s prototype chain):
const manish = new Person("Manish", 25);
console.log(manish instanceof Person); // true
console.log(manish instanceof Object); // true (everything inherits from Object)
console.log(manish instanceof Array); // false
Factory functions vs constructors
A factory function is just a regular function that returns an object. No new keyword, no this:
function createPerson(name, age) {
return {
name,
age,
greet() {
console.log(`Hi, I'm ${name}`); // closure, not this
}
};
}
const user = createPerson("Manish", 25);
The main trade-off: factory functions give us true privacy through closures, but constructor functions let us share methods via the prototype (more memory efficient). In modern JS, most people use classes (which are constructor functions under the hood) and #private fields for privacy.
In simple language, a constructor function is a blueprint for creating objects. The new keyword does the heavy lifting — it creates an empty object, sets up the prototype link, runs the function, and returns the result. Understanding these 4 steps is the key to understanding how JavaScript’s object system works.