JavaScript Prototypes and Prototype Inheritance
Prototypes are the foundation of how JavaScript objects share behavior. Every object in JavaScript has a hidden internal link called the prototype — a reference to another object from which it can inherit properties and methods.
Understanding prototypes helps explain how JavaScript truly works under the hood, and why features like toString() and hasOwnProperty() are available on every object without being defined manually.
What is a Prototype?
Every JavaScript object has a built-in property called [[Prototype]] (accessible via __proto__ or Object.getPrototypeOf()). This is a reference to another object. When a property or method is not found on an object, JavaScript automatically looks for it on the prototype — and then on the prototype's prototype, and so on. This chain is called the Prototype Chain.
let obj = { name: "Rohan" };
console.log(obj.name); // Rohan (own property)
console.log(obj.toString()); // [object Object] — found on Object.prototype
console.log(obj.hasOwnProperty("name")); // true — found on Object.prototypeThe Prototype Chain
// When obj.toString() is called:
// 1. JavaScript looks in obj — not found
// 2. JavaScript looks in obj.__proto__ (Object.prototype) — found!
// 3. Returns Object.prototype.toString()
let arr = [1, 2, 3];
// arr --> Array.prototype --> Object.prototype --> nullObject.prototype — The Root of All Objects
All objects ultimately inherit from Object.prototype. At the very top of every prototype chain is null.
let person = { name: "Priya" };
console.log(Object.getPrototypeOf(person) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // nullConstructor Functions and Prototypes
Before ES6 classes, constructor functions were the traditional way to create objects and set up inheritance. Classes are built on top of this same prototype system.
function Car(brand, speed) {
this.brand = brand;
this.speed = speed;
}
// Method added to prototype — shared by ALL Car instances
Car.prototype.move = function() {
console.log(`${this.brand} is moving at ${this.speed} km/h.`);
};
let car1 = new Car("Honda", 100);
let car2 = new Car("BMW", 200);
car1.move(); // Honda is moving at 100 km/h.
car2.move(); // BMW is moving at 200 km/h.
// Both instances share the same move method from the prototype
console.log(car1.move === car2.move); // trueWhy Add Methods to the Prototype?
If methods are defined inside the constructor function, a new copy is created for every instance — wasting memory. Adding methods to the prototype means all instances share a single copy.
function Person(name) {
this.name = name;
// Bad: a new copy of greet is created for each person
this.greet = function() { console.log("Hi, I am " + this.name); };
}
function Person(name) {
this.name = name;
}
// Good: shared by all instances through prototype
Person.prototype.greet = function() {
console.log("Hi, I am " + this.name);
};Object.create() — Set Prototype Explicitly
Object.create() creates a new object with the specified prototype object.
let animal = {
breathe() {
console.log(`${this.name} is breathing.`);
}
};
let dog = Object.create(animal);
dog.name = "Bruno";
dog.bark = function() {
console.log("Woof!");
};
dog.breathe(); // Bruno is breathing. (inherited from animal)
dog.bark(); // Woof! (own method)Prototype-Based Inheritance
Inheritance can be set up by linking the prototype of one constructor to an instance of another.
// Parent
function Vehicle(brand) {
this.brand = brand;
}
Vehicle.prototype.describe = function() {
console.log("Vehicle brand:", this.brand);
};
// Child
function Bike(brand, type) {
Vehicle.call(this, brand); // Call parent constructor
this.type = type;
}
// Link prototype chain
Bike.prototype = Object.create(Vehicle.prototype);
Bike.prototype.constructor = Bike;
// Add child-specific method
Bike.prototype.rideInfo = function() {
console.log(`${this.brand} is a ${this.type} bike.`);
};
let bike = new Bike("Royal Enfield", "Cruiser");
bike.describe(); // Vehicle brand: Royal Enfield
bike.rideInfo(); // Royal Enfield is a Cruiser bike.
console.log(bike instanceof Bike); // true
console.log(bike instanceof Vehicle); // trueHow ES6 Classes Relate to Prototypes
Classes in ES6 are syntactic sugar over the prototype system. Behind the scenes, they work exactly the same way as constructor functions and prototype chaining.
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound.`);
}
}
let a = new Animal("Tiger");
a.speak(); // Tiger makes a sound.
// Under the hood — speak() lives on Animal.prototype:
console.log(typeof Animal); // function
console.log(a.speak === Animal.prototype.speak); // trueChecking Properties — own vs inherited
hasOwnProperty() — Only Own Properties
function Laptop(brand) {
this.brand = brand;
}
Laptop.prototype.type = "Electronics";
let l = new Laptop("Dell");
console.log(l.hasOwnProperty("brand")); // true (own property)
console.log(l.hasOwnProperty("type")); // false (inherited from prototype)in Operator — Own and Inherited
console.log("brand" in l); // true
console.log("type" in l); // true (includes inherited)Adding Methods to Built-in Prototypes (Caution)
It is possible to add custom methods to built-in prototypes like Array.prototype or String.prototype. However, this is generally discouraged in production code as it can conflict with other libraries or future JavaScript updates.
// Example only — avoid doing this in production
String.prototype.capitalize = function() {
return this.charAt(0).toUpperCase() + this.slice(1);
};
console.log("hello".capitalize()); // HelloObject.keys() vs for...in for Prototype Properties
function Student(name) {
this.name = name;
}
Student.prototype.school = "eStudy247";
let s = new Student("Tara");
// for...in includes inherited properties
for (let key in s) {
console.log(key); // name, school
}
// Object.keys() only includes own properties
console.log(Object.keys(s)); // ["name"]Prototype vs Class — Side by Side
| Feature | Prototype Style | Class Style (ES6) |
|---|---|---|
| Object creation | Constructor function + new | class + new |
| Shared methods | Fn.prototype.method = function(){} | Defined inside class body |
| Inheritance | Object.create() + .call() | extends + super() |
| Readability | Verbose | Clean and clear |
| Under the hood | Prototype chain | Same prototype chain |
Key Points to Remember
- Every JavaScript object has a prototype — a reference to another object it inherits from
- When a property is not found on an object, JavaScript walks up the prototype chain to find it
- All objects ultimately inherit from
Object.prototype, which has a prototype ofnull - Add shared methods to
Constructor.prototypeto avoid duplicating functions per instance Object.create()creates an object with a specified prototype- ES6 classes are built on top of the same prototype system — they are syntactic sugar
- Use
hasOwnProperty()to check if a property belongs directly to an object, not inherited - Avoid modifying built-in prototypes in production code
