Inheritance in JavaScript: Extending Your Classes!

Inheritance in JavaScript: Extending Your Classes!

Imagine your family tree. You inherit traits from your parents, like your last name or eye color, and you may pass those traits down to your children. In JavaScript, inheritance works the same way: a parent class passes down properties and methods to its child classes.

Think of classes in JavaScript as family members. The parent class has certain traits (properties and methods), and the child class inherits those traits. But just like in real life, each child can also have their own unique characteristics.


What is Inheritance?

In programming, inheritance allows one class (the child) to borrow traits from another class (the parent). This helps you reuse code without needing to write the same thing over and over again. Just like you may have inherited your family’s last name, child classes can inherit methods like getFullName() from their parent class.


Inheritance in JavaScript – Using Prototypes

In JavaScript, inheritance is handled by something called prototypal inheritance. Each class has a prototype—an object that stores shared traits (methods and properties) that can be passed down to child classes. If a child class doesn’t have a certain method, JavaScript looks up the family tree, checking the prototype of the parent class.

Let’s break this down with our family tree theme:

  • The parent class holds the shared traits (like lastName or getFullName()).

  • The child class inherits these traits, but can also have its own traits (like favoriteToy).


Let’s Build a Family – Implementing Inheritance

Now, let’s create a family in JavaScript. We’ll start with a Person class (the parent) and then create a Child class that inherits from Person. The child will have all the traits of the parent, plus a few of their own.

Code Snippet: Building a Family with Inheritance

// Parent class (Person)
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Parent method
    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

// Child class inherits from Person
class Child extends Person {
    constructor(firstName, lastName, favoriteToy) {
        super(firstName, lastName);  // Inherit parent's properties
        this.favoriteToy = favoriteToy;  // Child's unique trait
    }

    // Child's unique method
    play() {
        console.log(`${this.getFullName()} is playing with ${this.favoriteToy}.`);
    }
}

// Create an instance of the Child class
const john = new Child("John", "Doe", "Lego");
john.play();  // Output: John Doe is playing with Lego.

Explanation:

  • Parent Class (Person): This class holds shared traits like firstName and lastName, along with the method getFullName() which returns the full name.

  • Child Class (Child): The child inherits these traits using the extends keyword and gets access to the getFullName() method. It also adds a new property favoriteToy and a method play() to showcase its unique behavior.

Think of Person as the parent who passes down the family name (lastName) to their child. The child class, Child, can also have its own traits like a favorite toy. The play() method lets the child show off this unique trait.


Family Tree Flowchart – How Inheritance Works

Here’s a simple family tree to show how inheritance works in JavaScript. Just like how family members share common traits, child classes in JavaScript inherit methods and properties from their parent class.

          Person (Parent Class)
            |  (Traits: firstName, lastName, getFullName())
          -----------------
          |               |
       Child          AnotherClass
   (Inherits from Person)   (Inherits from Person)
   (Adds favoriteToy and play())

In this flowchart:

  • The Person class acts as the parent, holding shared traits.

  • The Child class inherits these shared traits but adds its own traits, just like how children may inherit their parent’s last name but have their own hobbies or interests.


Challenge – Build a Family Tree Using Inheritance

Now it’s your turn! Let’s create a full family tree using inheritance in JavaScript. The challenge is to build out family members who share some traits but also have their own unique characteristics.

Challenge Instructions:

  1. Create a Person class: This will act as the base parent class. It should have firstName, lastName, and a method to display the full name (getFullName()).

  2. Create a Parent and Child class: Both should inherit from Person, but add their own properties and methods. For example, a Parent might have a job, and a Child might have a favoriteGame.

  3. Extend the family tree: Try adding more classes for other family members, like Grandparent or Sibling.

Example:

// Parent class
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

// Parent class inheriting from Person
class Parent extends Person {
    constructor(firstName, lastName, job) {
        super(firstName, lastName);
        this.job = job;
    }

    work() {
        console.log(`${this.getFullName()} is working as a ${this.job}.`);
    }
}

// Child class inheriting from Person
class Child extends Person {
    constructor(firstName, lastName, favoriteGame) {
        super(firstName, lastName);
        this.favoriteGame = favoriteGame;
    }

    play() {
        console.log(`${this.getFullName()} is playing ${this.favoriteGame}.`);
    }
}

// Creating instances
const mom = new Parent("Jane", "Doe", "Doctor");
const son = new Child("Jack", "Doe", "Hide and Seek");

mom.work();  // Output: Jane Doe is working as a Doctor.
son.play();  // Output: Jack Doe is playing Hide and Seek.

Passing Down Traits with Inheritance

In this first part, we’ve introduced inheritance using the family tree analogy, showing how classes in JavaScript can pass down traits (properties and methods) to their child classes. This allows us to create reusable code while giving each class its own unique behaviors. Inheritance makes your code more flexible and organized, just like how family traits are passed down through generations.

In the next part, we’ll dive deeper into the technical details of prototypal inheritance and explore how JavaScript handles inheritance behind the scenes.


Prototypal Inheritance – How JavaScript Does It Behind the Scenes

Previously, we looked at how classes and inheritance work, but there’s more happening behind the scenes. Prototypal inheritance is at the core of how JavaScript handles inheritance. Every object in JavaScript has an internal link to another object called its prototype.

Let’s dive into what that really means:

  • When you create an object or class, JavaScript automatically links it to a prototype object.

  • If you try to access a method or property that doesn’t exist on the object itself, JavaScript will look up the prototype chain to find it.

This prototype chain is what allows child classes to inherit methods and properties from their parent classes.


The Prototype Chain in Action

Let’s break down how the prototype chain works with an example. When you create a new object from a class, JavaScript connects that object to the class’s prototype. If the object doesn’t have a method or property, JavaScript checks the prototype, then the prototype’s prototype, and so on, until it finds what it’s looking for or reaches the end of the chain.

Code Snippet: Understanding the Prototype Chain

// Parent class
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

// Child class
class Child extends Person {
    constructor(firstName, lastName, favoriteToy) {
        super(firstName, lastName);
        this.favoriteToy = favoriteToy;
    }

    play() {
        console.log(`${this.getFullName()} is playing with ${this.favoriteToy}.`);
    }
}

const john = new Child("John", "Doe", "Lego");

// Let's explore the prototype chain
console.log(john);  // Shows the Child instance, which has a link to Person's prototype
console.log(john.__proto__);  // Links to the Child prototype
console.log(john.__proto__.__proto__);  // Links to Person's prototype

Explanation:

  • __proto__: This property (though not often used in code) shows the internal link to the object’s prototype.

  • The prototype chain starts with the john object (an instance of Child), which links to the Child prototype and then links to the Person prototype. If a method isn’t found on john, JavaScript looks up the chain to find it.


Overriding Methods – Customizing Child Class Behavior

Just like children can sometimes have different opinions from their parents, child classes can override methods from their parent class. This allows you to customize the behavior of a child class while still inheriting the general structure from the parent.

Code Snippet: Overriding a Parent Method in JavaScript

Let’s say we want to override the getFullName() method in the Child class to add the child’s favorite toy to their full name.

// Parent class
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

// Child class overriding the getFullName method
class Child extends Person {
    constructor(firstName, lastName, favoriteToy) {
        super(firstName, lastName);
        this.favoriteToy = favoriteToy;
    }

    // Overriding the parent's method
    getFullName() {
        return `${this.firstName} ${this.lastName} loves playing with ${this.favoriteToy}.`;
    }

    play() {
        console.log(`${this.getFullName()}`);
    }
}

const lucy = new Child("Lucy", "Smith", "Doll");
lucy.play();  // Output: Lucy Smith loves playing with Doll.

Explanation:

  • The Child class overrides the getFullName() method from the Person class.

  • Even though both the parent and child classes have the same method name (getFullName()), the child class version takes precedence when called on an instance of Child.


Challenge – Extending the Family Tree with Advanced Inheritance

Let’s reinforce the concept of inheritance by extending your family tree. In this challenge, you’ll create new family members that inherit traits from the Person class and add some more complexity by overriding methods and introducing custom behaviors.

Challenge Instructions:

  1. Create a Person class: This will act as the base parent class. It should have basic properties like firstName, lastName, and a method getFullName().

  2. Create subclasses for Parent and Child: Both should inherit from Person, but add their own unique properties and behaviors. For example, Parent might override the getFullName() method to include a job title, and Child might override it to include their favorite toy.

  3. Extend further: Create other family members like Grandparent or Sibling and see how you can reuse and override methods to add more flexibility.

Example:

// Base class
class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return `${this.firstName} ${this.lastName}`;
    }
}

// Parent class inherits and overrides
class Parent extends Person {
    constructor(firstName, lastName, job) {
        super(firstName, lastName);
        this.job = job;
    }

    // Overriding getFullName to include the job
    getFullName() {
        return `${this.firstName} ${this.lastName} works as a ${this.job}.`;
    }

    work() {
        console.log(this.getFullName());
    }
}

// Child class inherits and overrides
class Child extends Person {
    constructor(firstName, lastName, favoriteGame) {
        super(firstName, lastName);
        this.favoriteGame = favoriteGame;
    }

    // Overriding getFullName to include the favorite game
    getFullName() {
        return `${this.firstName} ${this.lastName} loves playing ${this.favoriteGame}.`;
    }

    play() {
        console.log(this.getFullName());
    }
}

// Instances
const father = new Parent("James", "Brown", "Engineer");
const son = new Child("Tom", "Brown", "Chess");

father.work();  // Output: James Brown works as an Engineer.
son.play();     // Output: Tom Brown loves playing Chess.

Conclusion – Extending Functionality Through Inheritance

We’ve explored the deeper workings of inheritance in JavaScript through the prototype chain and learned how to override methods in child classes. You’ve seen how inheritance can make your code more flexible and maintainable by allowing you to share common traits while customizing behavior where needed.

By mastering inheritance, you’re now equipped to build more advanced JavaScript applications with clean, reusable, and scalable code.


Let’s tackle Polymorphism next!

In the next article, we’ll explore polymorphism—an advanced OOP concept that allows objects to be treated as instances of their parent class, giving you even more flexibility in how you design your programs.