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

How does prototypal inheritance differ from classical inheritance? Explain the prototype chain and how you would implement inheritance in modern JavaScript.

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