Prototypes & Prototype Chain

Difficulty: Intermediate

Question

Explain prototypal inheritance in JavaScript. What is the prototype chain?

Answer

JavaScript uses prototypal inheritance, where objects can inherit properties and methods from other objects. Every object has an internal [[Prototype]] link (accessible via __proto__ or Object.getPrototypeOf()) that points to another object.

When you access a property on an object, JavaScript first looks at the object itself. If not found, it follows the prototype chain - checking the object's prototype, then the prototype's prototype, and so on until it reaches null (the end of the chain).

Functions have a 'prototype' property (not to be confused with __proto__). When you use 'new' to create an instance, the instance's __proto__ is set to the constructor's prototype. This is how methods are shared across all instances without duplicating them in memory.

Object.create() lets you create objects with a specific prototype, enabling clean prototypal inheritance without constructors.

Code examples

Prototype Chain Basics

const animal = {
  type: 'Animal',
  speak() {
    return `${this.name} says ${this.sound}`;
  }
};

const dog = Object.create(animal);
dog.name = 'Rex';
dog.sound = 'Woof';

console.log(dog.speak());        // Uses inherited method
console.log(dog.type);           // Inherited property
console.log(dog.hasOwnProperty('name'));  // true (own)
console.log(dog.hasOwnProperty('type'));  // false (inherited)

// Walking the prototype chain
console.log(Object.getPrototypeOf(dog) === animal); // true
console.log(Object.getPrototypeOf(animal) === Object.prototype); // true
console.log(Object.getPrototypeOf(Object.prototype)); // null (end)

dog inherits from animal, which inherits from Object.prototype. Property lookup walks up this chain until it finds the property or reaches null.

Constructor Functions and prototype

function Person(name, age) {
  this.name = name;
  this.age = age;
}

// Methods on prototype are shared across all instances
Person.prototype.greet = function() {
  return `Hi, I'm ${this.name}, age ${this.age}`;
};

const alice = new Person('Alice', 25);
const bob = new Person('Bob', 30);

console.log(alice.greet());
console.log(bob.greet());

// Both share the same method reference
console.log(alice.greet === bob.greet); // true

// Instance vs prototype relationship
console.log(alice.__proto__ === Person.prototype); // true
console.log(alice instanceof Person); // true
console.log(alice.constructor === Person); // true

Methods defined on the prototype are shared (one copy in memory). Each instance's __proto__ points to Person.prototype.

Prototype-based Inheritance

function Shape(color) {
  this.color = color;
}
Shape.prototype.describe = function() {
  return `A ${this.color} ${this.type}`;
};

function Circle(color, radius) {
  Shape.call(this, color); // Call parent constructor
  this.type = 'circle';
  this.radius = radius;
}

// Set up inheritance chain
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;

Circle.prototype.area = function() {
  return Math.PI * this.radius  2;
};

const c = new Circle('red', 5);
console.log(c.describe());              // inherited from Shape
console.log(c.area().toFixed(2));       // own method
console.log(c instanceof Circle);       // true
console.log(c instanceof Shape);        // true

Shape.call(this, color) borrows the parent constructor. Object.create(Shape.prototype) sets up the chain so Circle instances can access Shape methods.

Key points

Concepts covered

Prototype, Prototype Chain, __proto__, Object.create, Inheritance, Constructor