Problem Statement
Explain prototypal inheritance in JavaScript. How does it differ from classical inheritance?
Explanation
JavaScript uses prototypal inheritance, which is fundamentally different from classical inheritance used in languages like Java or C++.
Prototypal inheritance:
Objects inherit directly from other objects, not from classes. Every object has a hidden link to another object called its prototype. When you access a property, JavaScript looks in the object first, then follows the prototype chain until found or reaching null.
How it works:
Each object has an internal prototype property accessible via underscore underscore proto underscore or Object.getPrototypeOf. Functions have a prototype property used when creating objects with new. New objects created from a constructor get that constructor's prototype as their prototype. Properties and methods can be shared through the prototype chain.
Classical inheritance:
Classes are blueprints for objects. Objects are instances of classes. Inheritance creates is-a relationships. Classes inherit from other classes using extends keyword.
Key differences:
JavaScript has no true classes, only objects. ES6 classes are syntactic sugar over prototypes. In classical OOP, you define classes then create instances. In prototypal OOP, you create objects that serve as prototypes for other objects. Prototypal inheritance is more flexible but can be confusing.
Advantages of prototypal inheritance:
More flexible and dynamic. Can change prototypes at runtime. Memory efficient by sharing methods. Simpler mental model, everything is an object.
Disadvantages:
Can be confusing for developers from classical OOP backgrounds. Easier to make mistakes with prototype chain. Less structure than class-based systems.
Best practices:
Use ES6 classes for cleaner syntax. Understand prototypes underneath. Share methods through prototype, not in constructor. Avoid modifying built-in prototypes.
Understanding prototypal inheritance is essential for mastering JavaScript and common in interviews.
Code Solution
SolutionRead Only
// PROTOTYPAL INHERITANCE
// Objects inherit from objects
const animalProto = {
eat: function() {
console.log(this.name + ' is eating');
}
};
const dog = Object.create(animalProto);
dog.name = 'Buddy';
dog.bark = function() {
console.log(this.name + ' says woof!');
};
dog.eat(); // 'Buddy is eating' (inherited)
dog.bark(); // 'Buddy says woof!' (own)
// Prototype chain
console.log(Object.getPrototypeOf(dog) === animalProto); // true
// CONSTRUCTOR FUNCTION PATTERN
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating');
};
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor
this.breed = breed;
}
// Set up inheritance
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(this.name + ' says woof!');
};
const buddy = new Dog('Buddy', 'Golden Retriever');
buddy.eat(); // 'Buddy is eating'
buddy.bark(); // 'Buddy says woof!'
// Prototype chain: buddy -> Dog.prototype -> Animal.prototype -> Object.prototype -> null
// ES6 CLASS SYNTAX (still uses prototypes)
class AnimalClass {
constructor(name) {
this.name = name;
}
eat() {
console.log(this.name + ' is eating');
}
}
class DogClass extends AnimalClass {
constructor(name, breed) {
super(name);
this.breed = breed;
}
bark() {
console.log(this.name + ' says woof!');
}
}
const max = new DogClass('Max', 'Labrador');
max.eat(); // 'Max is eating'
max.bark(); // 'Max says woof!'
// Still using prototypes underneath
console.log(max.__proto__ === DogClass.prototype); // true
console.log(DogClass.prototype.__proto__ === AnimalClass.prototype); // true
// CLASSICAL INHERITANCE (for comparison)
// In Java:
// class Animal {
// String name;
// void eat() { ... }
// }
// class Dog extends Animal {
// String breed;
// void bark() { ... }
// }
// Dog dog = new Dog();
// Key differences:
// 1. JavaScript: Objects inherit from objects
// Classical: Classes inherit from classes
// 2. JavaScript: Dynamic prototype chain
// Classical: Fixed class hierarchy
// 3. JavaScript: Can modify prototypes at runtime
// Classical: Cannot modify classes after definition
// Memory efficiency
function Person(name) {
this.name = name;
// Bad: Each instance gets its own copy
// this.greet = function() { console.log('Hi'); };
}
// Good: Shared across all instances
Person.prototype.greet = function() {
console.log('Hi, I am ' + this.name);
};
const p1 = new Person('John');
const p2 = new Person('Jane');
// Same method reference (memory efficient)
console.log(p1.greet === p2.greet); // true