Inheritance in JavaScript: A Complete Guide

JavaScript
5 minutes read

Introduction

Hello, developers! Today, we are exploring a crucial concept in programming – Inheritance. Inheritance is a significant part of object-oriented programming (OOP) that allows us to write reusable and efficient code.

In a simple sense, inheritance enables a class or constructor to inherit the properties and methods of another class, making those features part of its own structure. Just as children may inherit their parents’ hair or eye color, so too can one JavaScript object inherit the characteristics of another.

What is the concept of inheritance?

In programming, inheritance is like a family tree. Just like you might inherit your parents’ hair color or height, in programming, one class (let’s call it the “child” class) can inherit properties and methods from another class (the “parent” class). This is a great way to reuse code, and it makes it easier to maintain and organize your program.

Here’s a simplified example in JavaScript:

// Here we have a class named Animal. Think of it as the parent.
class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating.`);
  }
}

// Now, we have a Dog class. It's like a child in our family tree. 
// We're using the keyword 'extends' to say that Dog is a kind of Animal.
class Dog extends Animal {
  bark() {
    console.log(`${this.name} is barking.`);
  }
}

let myDog = new Dog('Rex');
myDog.eat();  // Output: "Rex is eating."
myDog.bark(); // Output: "Rex is barking."

In this example, Dog is a subclass of Animal (i.e., it inherits from Animal). We can create a Dog instance and call the eat method, which is defined in the Animal class. The Dog class didn’t have to define eat for itself; it inherited it from Animal.

So, in simple words, inheritance in programming is like how you might inherit characteristics from your parents. It’s a way for one type of thing (a class) to get properties or abilities from another type of thing. This makes programming easier because you don’t have to rewrite the same code over and over again. You can just say “This thing is a type of that thing” and automatically get all its properties and abilities.

Inheritance in JavaScript

Unlike languages such as Java or C++, JavaScript doesn’t use class-based inheritance. Instead, it uses “Prototypal Inheritance.” All objects in JavaScript have an internal property known as the [[Prototype]], which links to another object. This prototype object has a prototype of its own, , and the chain continues until we reach an object with a null prototype.

We call the chain from the object to its prototype to the prototype’s prototype the prototype chain. When you request a property or method of an object, JavaScript will first look at the object’s own properties. If it does not find it there, it looks at the properties of the object’s prototype. If it’s not there either, JavaScript continues up the prototype chain.

const animal = {
  makesSound: true,
};

const dog = Object.create(animal);
console.log(dog.makesSound); // true

In the code snippet above, the dog object does not have the makesSound property on its own, but it has access to it because it’s in its prototype, the animal object.

Implementing Inheritance in JavaScript

There are several ways to establish this inheritance relationship between objects in JavaScript.

1- Using Constructor Functions:

Constructor functions are special functions that create new objects. They define a blueprint for a type of object: what properties it should have, what methods it should have, etc.

function Animal (name) {
  this.name = name;
}

Animal.prototype.speak = function () {
  console.log(this.name + ' makes a sound.');
}

function Dog (name) {
  Animal.call(this, name);
}

Dog.prototype = Object.create(Animal.prototype);

const dog = new Dog('Rex');
dog.speak(); // Rex makes a sound.

In the code above, the Animal constructor function is defined with a name property and a speak method. The Dog constructor function is defined, which calls the Animal constructor function to initialize its name property. The Dog prototype is then set to a new object whose prototype is the Animal prototype, establishing the inheritance relationship.

2- Using Object.create() Method

The Object.create() method is used to create a new object and set its prototype to an existing object.

const animal = {
  speak: function () {
    console.log('Makes a sound');
  }
};

const dog = Object.create(animal);
dog.speak(); // Makes a sound.

Here, animal is an object with a speak method. The dog object is created with animal as its prototype. Thus, dog can access the speak method through the prototype chain.

3- Using ES6 Classes and the extends Keyword

ES6 Classes provide a much more straightforward and clear syntax for creating constructor functions and handling a prototype-based inheritance. The extends keyword is used to create a subclass.

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.

In the above code, Animal is a base class that has a name property and a speak method. Dog is a subclass that extends Animal. It overrides the speak method to provide its version. When an instance of Dog is created and the speak method is called, it uses the Dog class’s speak method.

Key Concepts and Precautions in JavaScript Inheritance

While JavaScript inheritance provides powerful capabilities, there are concepts and precautions to be aware of.

Constructor and Instance

Every JavaScript function comes with a prototype property, used when creating new instances. These instances have access to properties and methods defined on the constructor’s prototype.

function Animal(name) {
    this.name = name;
}

Animal.prototype.speak = function () {
    console.log(this.name + ' makes a noise.');
}

const dog = new Animal('Rex');
dog.speak(); // Rex makes a noise.

In this example, Animal is the constructor, and dog is an instance of Animal. The dog instance has access to the speak method defined on the Animal's prototype.

ES6 Classes and Inheritance

ES6 classes are merely syntactic sugar over JavaScript’s existing prototype-based inheritance. They provide a cleaner syntax but do not introduce a new inheritance model.

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.

This is the same example as before but written using ES6 class syntax. This shows that ES6 classes are just a different way to write prototype-based inheritance.

Prototype Pollution

Manipulating prototypes can lead to prototype pollution. If someone changes properties on the Object prototype, it can lead to application-wide modifications

Object.prototype.foo = "bar";

const myObject = {};

console.log(myObject.foo); // "bar"

Here, we’ve added a foo property to Object.prototype. Every object in JavaScript is a descendant of Object, so this property is now available on all objects, potentially leading to unintended consequences.

Performance Considerations

Property lookups can be slower for properties deep in the prototype chain. JavaScript engines have to traverse the prototype chain to find properties, which can take time.

const grandparent = { foo: 'hello' };
const parent = Object.create(grandparent);
const child = Object.create(parent);

console.log(child.foo); // "hello"

In this example, foo is not a property of child or parent, so the JavaScript engine must go up the prototype chain to grandparent to find foo.

Has Own Property

The hasOwnProperty() method can be used to check if an object has a specific property as its own property and not an inherited one.

const parent = { foo: 'hello' };
const child = Object.create(parent);

console.log(child.hasOwnProperty('foo')); // false

Here, even though child.foo would return hello, the hasOwnProperty method correctly reports that foo is not child‘s own property; it is an inherited property from parent.

Further Reading

Conclusion

Inheritance in JavaScript, with its prototype-based approach, might seem a bit strange if you’re coming from a class-based language. However, it is a powerful feature that allows for unique and flexible design patterns. Through a deeper understanding and clever utilization of inheritance, you can write efficient, organized, and clean code in your JavaScript applications. Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *