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:

  1. 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'
  1. 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'
  1. 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