JavaScript Object-Oriented Programming: Inheritance

JavaScript Object-Oriented Programming: Inheritance

Inheritance is a core concept in object-oriented programming (OOP) that allows one class to inherit properties and methods from another. In JavaScript, inheritance lets developers create new classes that build upon existing ones, promoting code reuse and logical structure. When a class inherits from another, it can access and use the parent’s features while also adding or changing its own behavior. This concept closely mirrors real-world relationships, such as how a dog is an animal but has its own unique traits.

JavaScript supports inheritance through its prototype system, but modern syntax uses class and extends keywords to express inheritance clearly and simply. This article will guide you through the process of using inheritance in JavaScript, focusing on practical “how-to” steps with engaging, runnable examples that will make the concept clear and fun to apply.

Creating a Base Class

To begin understanding inheritance, we first need a base class. This class acts like a blueprint for other classes that will extend it. Consider an Animal class which will have basic properties and methods that all animals share.

class Animal {

  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }

}

Here, the Animal class has a constructor that accepts a name and a method speak that outputs a simple message. This class can now be used as a foundation to create more specific animal types.

By running this code, you have a class with one property (name) and one behavior (speak). Any class that inherits from Animal will automatically have these features unless changed.

Extending a Class Using extends

Inheritance happens when a new class extends an existing class. This means the new class will inherit all properties and methods from the original. For example, let’s create a Dog class that inherits from Animal.

class Dog extends Animal {}

const myDog = new Dog('Kol');
myDog.speak();

In this code, Dog extends Animal but adds nothing new yet. When we create a Dog object named “Kol” and call speak(), it uses the inherited method from Animal, printing “Kol makes a sound.”

This simple extension shows that Dog now behaves like an Animal, having all its properties and methods without any extra code.

Calling the Parent Class Constructor with super()

When you extend a class, the subclass needs to call the parent class’s constructor to properly initialize inherited properties. This is done using super() inside the subclass constructor.

class Dog extends Animal {

  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

}

const myDog = new Dog('Kol', 'Beagle');
console.log(`${myDog.name} is a ${myDog.breed}.`);

Here, the Dog constructor takes name and breed. It uses super(name) to call the Animal constructor, setting the name. Then, it sets the breed property unique to Dog.

Running this code will output: “Kol is a Beagle.” This shows how inheritance combines shared properties from the parent with subclass-specific properties.

Adding New Properties and Methods in Subclass

Subclasses can add their own properties and methods on top of what they inherit. Let’s add a new method bark() to Dog.

class Dog extends Animal {

  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  bark() {
    console.log(`${this.name} barks loudly!`);
  }

}

const myDog = new Dog('Kol', 'Beagle');
myDog.speak();
myDog.bark();

This example shows Dog having both inherited method speak() and new method bark(). The bark() method prints a dog-specific message. This demonstrates how subclass objects combine inherited and unique features seamlessly.

Overriding Parent Methods in Subclass

Subclasses can override methods they inherit to provide their own behavior. This means defining a method with the same name as the parent but with a different implementation.

class Dog extends Animal {

  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} says: Woof!`);
  }

}

const myDog = new Dog('Kol', 'Beagle');
myDog.speak();

Here, Dog overrides the speak() method to print a more dog-like message. When calling myDog.speak(), the overridden method runs instead of the parent’s. This allows subclasses to specialize inherited behavior.

Using super to Call Parent Methods from Subclass

Sometimes you want to override a method but still use the parent’s original behavior. You can do this by calling super.methodName() inside the overridden method.

class Dog extends Animal {

  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  speak() {
    super.speak();
    console.log(`${this.name} also says: Woof!`);
  }

}

const myDog = new Dog('Kol', 'Beagle');
myDog.speak();

In this code, Dog’s speak() first calls Animal’s speak() using super.speak(), then adds an extra line. It shows how you can combine parent and child behavior in one method.

Multiple Levels of Inheritance

Inheritance can be chained through multiple levels. For example, ServiceDog can inherit from Dog, which inherits from Animal.

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;
  }

  bark() {
    console.log(`${this.name} barks loudly!`);
  }

}

class ServiceDog extends Dog {

  constructor(name, breed, job) {
    super(name, breed);
    this.job = job;
  }

  performJob() {
    console.log(`${this.name} is performing the job: ${this.job}`);
  }

}

const myServiceDog = new ServiceDog('Max', 'Labrador', 'guide dog');
myServiceDog.speak();
myServiceDog.bark();
myServiceDog.performJob();

Here, ServiceDog adds a job property and performJob() method. It inherits speak() from Dog, which inherits from Animal. This shows how inheritance flows through multiple generations of classes.

Fun Example: Magical Creature Inheritance

To make it more exciting, let’s build a magical creature hierarchy: CreatureWizardFireWizard.

class Creature {

  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`${this.name} says hello.`);
  }

}

class Wizard extends Creature {

  constructor(name, magicType) {
    super(name);
    this.magicType = magicType;
  }

  castSpell() {
    console.log(`${this.name} casts a ${this.magicType} spell.`);
  }

}

class FireWizard extends Wizard {

  castSpell() {
    super.castSpell();
    console.log(`${this.name} casts a blazing fireball!`);
  }

}

const harry = new FireWizard('Harry', 'fire');
harry.greet();
harry.castSpell();

This example builds three levels of inheritance. FireWizard overrides castSpell() but calls the parent version first. It’s a vivid example of inheritance in action, with fun magical storytelling.

Conclusion

Inheritance in JavaScript lets you create class hierarchies that share and extend behavior. Using extends and super(), subclasses can inherit properties and methods, add their own, override parent methods, and even call the original parent method. This structure helps keep code organized and expressive, just like natural relationships in the world.

By understanding and practicing inheritance, you gain a powerful tool to build complex and reusable JavaScript programs.

References

If you want to learn more about JavaScript inheritance and related topics, these resources are excellent starting points:

Scroll to Top