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.

prototypal-inheritance.js
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 inherited greet method from the person object.

Constructor Functions

Constructor functions provide a way to create multiple instances of an object type.

consstructor-functions.js
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 a name property.
  • Person.prototype.greet: Adds a method to the Person prototype that can be called on all instances of Person.
  • new Person('John'): Creates a new instance of Person with the name John.

ES6 Classes

ES6 introduced classes, which are syntactic sugar over the existing prototypal inheritance.

classes.js
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 class Person with a constructor and a greet method.
  • constructor(name): The constructor initializes the name property.
  • new Person('John'): Creates a new instance of the Person class with the name John.

Extending Classes

Classes can be extended to create subclasses.

extending-classes.js
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 class Employee that extends the Person class.
  • super(name): Calls the constructor of the parent class Person.
  • john.work(): Calls the work method defined in the Employee class.

Composition over Inheritance

In some cases, composition can be a better alternative to inheritance. Composition allows you to create complex types by combining objects.

composition.js
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 an eat method.
  • canWalk: An object with a walk method.
  • Object.assign({}, canEat, canWalk): Creates a new object that combines properties from canEat and canWalk.

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.

example.js
// 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: The Dog class extends the Animal class and overrides the speak method.
  • Composition example: The dog object combines behaviors from canEat, canWalk, and canBark.

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.