Object Inheritance
Object inheritance is a fundamental concept in JavaScript, allowing objects to inherit properties and methods from other objects. This is typically done using prototypes.
Prototypal Inheritance
Prototypal inheritance in JavaScript allows an object to inherit properties and methods from another object.
const person = {
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
};
const john = Object.create(person);
john.name = 'John';
john.greet(); // "Hello, my name is John"
Object.create(): Creates a new object with the specified prototype object and properties.john.greet(): Calls the inheritedgreetmethod from thepersonobject.
Constructor Functions
Constructor functions provide a way to create multiple instances of an object type.
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}`);
};
const john = new Person('John');
const jane = new Person('Jane');
john.greet(); // "Hello, my name is John"
jane.greet(); // "Hello, my name is Jane"
Person: Constructor function that initializes a new object with anameproperty.Person.prototype.greet: Adds a method to thePersonprototype that can be called on all instances ofPerson.new Person('John'): Creates a new instance ofPersonwith the nameJohn.
ES6 Classes
ES6 introduced classes, which are syntactic sugar over the existing prototypal inheritance.
class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}
const john = new Person('John');
const jane = new Person('Jane');
john.greet(); // "Hello, my name is John"
jane.greet(); // "Hello, my name is Jane"
class Person: Declares a classPersonwith a constructor and agreetmethod.constructor(name): The constructor initializes thenameproperty.new Person('John'): Creates a new instance of thePersonclass with the nameJohn.
Extending Classes
Classes can be extended to create subclasses.
class Person {
  constructor(name) {
    this.name = name;
  }
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}
class Employee extends Person {
  constructor(name, jobTitle) {
    super(name);
    this.jobTitle = jobTitle;
  }
  work() {
    console.log(`${this.name} is working as a ${this.jobTitle}`);
  }
}
const john = new Employee('John', 'Developer');
john.greet(); // "Hello, my name is John"
john.work(); // "John is working as a Developer"
class Employee extends Person: Declares a classEmployeethat extends thePersonclass.super(name): Calls the constructor of the parent classPerson.john.work(): Calls theworkmethod defined in theEmployeeclass.
Composition over Inheritance
In some cases, composition can be a better alternative to inheritance. Composition allows you to create complex types by combining objects.
const canEat = {
  eat() {
    console.log('Eating');
  }
};
const canWalk = {
  walk() {
    console.log('Walking');
  }
};
const person = Object.assign({}, canEat, canWalk);
person.eat(); // "Eating"
person.walk(); // "Walking"
canEat: An object with aneatmethod.canWalk: An object with awalkmethod.Object.assign({}, canEat, canWalk): Creates a new object that combines properties fromcanEatandcanWalk.
Practical Examples and Best Practices
Using inheritance and composition in practical scenarios can help you design more flexible and reusable code. Here are some examples and best practices:
Inheritance: Use inheritance when you have a clear hierarchy and shared behavior that can be reused.
Composition: Use composition when you need to combine behaviors or when inheritance doesn't fit well with your design.
// Inheritance example
class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}
class Dog extends Animal {
  speak() {
    console.log(`${this.name} barks.`);
  }
}
const dog = new Dog('Rex');
dog.speak(); // "Rex barks."
// Composition example
const canBark = {
  bark() {
    console.log('Barking');
  }
};
const dog = Object.assign({}, canEat, canWalk, canBark);
dog.eat(); // "Eating"
dog.walk(); // "Walking"
dog.bark(); // "Barking"
Inheritance example: TheDogclass extends theAnimalclass and overrides thespeakmethod.Composition example: Thedogobject combines behaviors fromcanEat,canWalk, andcanBark.
FAQ
Q: When should I use inheritance?
A: Use inheritance when you have a clear hierarchy and shared behavior that can be reused across multiple subclasses. Inheritance works well when there is a "is-a" relationship between the parent and child classes.
Q: When should I use composition?
A: Use composition when you need to combine behaviors or when inheritance doesn't fit well with your design. Composition is useful when there is a "has-a" relationship, allowing you to create more flexible and reusable components.
Q: Can I mix inheritance and composition?
A: Yes, you can mix inheritance and composition to create more complex and flexible designs. Use inheritance for shared behavior and hierarchy, and use composition to combine different behaviors as needed.
