How does prototypal inheritance differ from classical inheritance? Explain the prototype chain and how you would implement inheritance in modern JavaScript.
3 minintermediatejavascriptprototypalinheritancedifferclassical
Quick Answer
Every object in JavaScript has a prototype property that points to another object. When you access a property, JavaScript looks up the prototype chain until it finds the property or reaches null.
Detailed Answer
Classical vs Prototypal Inheritance:
Classical Inheritance:
- Based on classes (blueprints)
- Objects are instances of classes
- Inheritance is defined at compile time
- Single inheritance (one parent class)
- Found in Java, C++, C#
Prototypal Inheritance:
- Based on prototypes (objects)
- Objects inherit directly from other objects
- Inheritance is dynamic and flexible
- Can have multiple prototypes (mixins)
- Found in JavaScript
Prototype Chain:
Every object in JavaScript has a prototype property that points to another object. When you access a property, JavaScript looks up the prototype chain until it finds the property or reaches null.
// Prototype chain example
const animal = {
type: 'animal',
speak() {
console.log('Some sound');
}
};
const dog = Object.create(animal);
dog.breed = 'Labrador';
dog.speak = function() {
console.log('Woof!');
};
const puppy = Object.create(dog);
puppy.age = 2;
console.log(puppy.type); // 'animal' (from animal prototype)
console.log(puppy.breed); // 'Labrador' (from dog prototype)
console.log(puppy.age); // 2 (own property)
puppy.speak(); // 'Woof!' (from dog prototype)
Modern JavaScript Inheritance Implementation:
- Using ES6 Classes (Syntactic Sugar):
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
console.log(`${this.name} barks`);
}
fetch() {
console.log(`${this.name} fetches the ball`);
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.speak(); // 'Buddy barks'
dog.fetch(); // 'Buddy fetches the ball'
- Using Object.create() (True Prototypal):
// Base object
const animal = {
init(name) {
this.name = name;
return this;
},
speak() {
console.log(`${this.name} makes a sound`);
}
};
// Create dog object that inherits from animal
const dog = Object.create(animal);
dog.init = function(name, breed) {
animal.init.call(this, name);
this.breed = breed;
return this;
};
dog.speak = function() {
console.log(`${this.name} barks`);
};
dog.fetch = function() {
console.log(`${this.name} fetches the ball`);
};
const buddy = Object.create(dog).init('Buddy', 'Golden Retriever');
buddy.speak(); // 'Buddy barks'
buddy.fetch(); // 'Buddy fetches the ball'
- Using Factory Functions:
function createAnimal(name) {
return {
name,
speak() {
console.log(`${this.name} makes a sound`);
}
};
}
function createDog(name, breed) {
const dog = createAnimal(name);
dog.breed = breed;
const originalSpeak = dog.speak;
dog.speak = function() {
console.log(`${this.name} barks`);
};
dog.fetch = function() {
console.log(`${this.name} fetches the ball`);
};
return dog;
}
const buddy = createDog('Buddy', 'Golden Retriever');
buddy.speak(); // 'Buddy barks'
buddy.fetch(); // 'Buddy fetches the ball'
Key Differences:
- Flexibility: Prototypal inheritance is more flexible - you can add/remove properties at runtime
- Memory: Prototypal inheritance can be more memory efficient as methods are shared
- Multiple Inheritance: Easier to achieve with mixins in prototypal inheritance
- Performance: Modern engines optimize both approaches similarly