Skip to main content

Prototypal Inheritance Explained

Answer

JavaScript uses prototypal inheritance, where objects inherit directly from other objects. Every object has an internal link to another object called its prototype.

Prototype Chain

How It Works

// When accessing a property:
const person = {
name: "John",
greet() {
return `Hello, I'm ${this.name}`;
},
};

const employee = Object.create(person);
employee.role = "Developer";

console.log(employee.role); // "Developer" (own property)
console.log(employee.name); // "John" (from prototype)
console.log(employee.greet()); // "Hello, I'm John"

// Check where property comes from
console.log(employee.hasOwnProperty("role")); // true
console.log(employee.hasOwnProperty("name")); // false

Constructor Functions

// Constructor function (pre-ES6 way)
function Person(name, age) {
this.name = name;
this.age = age;
}

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

Person.prototype.isAdult = function () {
return this.age >= 18;
};

const john = new Person("John", 30);
const jane = new Person("Jane", 25);

console.log(john.greet()); // "Hi, I'm John"
console.log(jane.greet()); // "Hi, I'm Jane"

// Both share the same greet function
console.log(john.greet === jane.greet); // true

ES6 Classes (Syntactic Sugar)

// Classes are just syntactic sugar over prototypes
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}

greet() {
return `Hi, I'm ${this.name}`;
}

static createAnonymous() {
return new Person("Anonymous", 0);
}
}

class Employee extends Person {
constructor(name, age, role) {
super(name, age);
this.role = role;
}

greet() {
return `${super.greet()}, I work as a ${this.role}`;
}
}

const dev = new Employee("John", 30, "Developer");
console.log(dev.greet()); // "Hi, I'm John, I work as a Developer"

Prototype vs proto vs prototype

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

const john = new Person("John");

// prototype: Property of constructor functions
console.log(Person.prototype); // { constructor: Person }

// __proto__: Reference to object's prototype (deprecated, use Object.getPrototypeOf)
console.log(john.__proto__ === Person.prototype); // true
console.log(Object.getPrototypeOf(john) === Person.prototype); // true

// All instances share the same prototype
const jane = new Person("Jane");
console.log(john.__proto__ === jane.__proto__); // true

Property Lookup Chain

const animal = {
eats: true,
walk() {
return "Walking...";
},
};

const rabbit = Object.create(animal);
rabbit.jumps = true;

rabbit.walk = function () {
return "Hopping!"; // Overrides parent
};

console.log(rabbit.jumps); // true (own property)
console.log(rabbit.eats); // true (inherited)
console.log(rabbit.walk()); // "Hopping!" (own method shadows parent)

Object.create() Patterns

// Create object with specific prototype
const personMethods = {
greet() {
return `Hello, ${this.name}`;
},
introduce() {
return `I am ${this.name}, ${this.age} years old`;
},
};

function createPerson(name, age) {
const person = Object.create(personMethods);
person.name = name;
person.age = age;
return person;
}

const john = createPerson("John", 30);
console.log(john.greet()); // "Hello, John"

Checking Prototypes

const arr = [1, 2, 3];

// instanceof checks prototype chain
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true

// isPrototypeOf checks if object is in chain
console.log(Array.prototype.isPrototypeOf(arr)); // true
console.log(Object.prototype.isPrototypeOf(arr)); // true

// Get and set prototype
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true

// Set prototype (not recommended for performance)
const proto = { greet: () => "Hi" };
const obj = {};
Object.setPrototypeOf(obj, proto);

Comparison with Classical Inheritance

Classical (Java/C++)Prototypal (JavaScript)
Classes define blueprintsObjects inherit from objects
Copy of class creates objectObjects link to prototype
Rigid hierarchyFlexible, dynamic
Methods copied to each instanceMethods shared on prototype

Performance Note

// ✅ Methods on prototype (shared, memory efficient)
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
return `Hi, ${this.name}`;
};

// ❌ Methods in constructor (copied to each instance)
function Person(name) {
this.name = name;
this.greet = function () {
// New function per instance!
return `Hi, ${this.name}`;
};
}

Key Points

  • Objects inherit directly from other objects
  • Prototype chain is followed until null
  • Properties on object shadow prototype properties
  • Methods should be on prototype for efficiency
  • ES6 classes are syntactic sugar over prototypes
  • Use Object.getPrototypeOf() instead of __proto__