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.prototype

The 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 --> null

Object.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));            // null

Constructor 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); // true

Why 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); // true

How 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); // true

Checking 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()); // Hello

Object.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

FeaturePrototype StyleClass Style (ES6)
Object creationConstructor function + newclass + new
Shared methodsFn.prototype.method = function(){}Defined inside class body
InheritanceObject.create() + .call()extends + super()
ReadabilityVerboseClean and clear
Under the hoodPrototype chainSame 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 of null
  • Add shared methods to Constructor.prototype to 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

Leave a Comment

Your email address will not be published. Required fields are marked *