The community is working on translating this tutorial into Polish, but it seems that no one has started the translation process for this article yet. If you can help us, then please click "More info".
Inheritance
In object oriented programming, inheritance is a very important concept. It allows you to build a class on top of another class and thereby extending and/or modifying the functionality, without changing the base class.
This makes great sense when looking at the real world, where you will see a lot of things that are basically just a slightly different version of something else. For instance, a dog and a cat are considered as two very different things, but they are both animals and they both have four legs and a tail. Another example, perhaps more related to programming and websites/applications in general, is a user and an administrator. They will likely be able to interact with a system in different ways, but they probably both have a name and an e-mail address.
Inheritance in JavaScript is easy to do, and very flexible. In this article, we'll dig deeper into this powerful concept.
Animals
So far in this tutorial, we have used a Dog class as an example many times. But if we were to create a system to handle more animals than just dogs, it would make sense to have an Animal class and then let our Dog class extend it. So first, let's create an Animal class:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm sort of animal and I have " + this.numberOfLegs + " legs...";
}
}
let animal = new Animal(4);
alert(animal.Describe());
This is now our base class for handling animals. As you can see, it's very simple - actually, it only has a field for telling how many legs this animal have (0 by default) and a method for describing the animal. We can of course instantiate this class and use these features, just like any other class. But now, let's try extending it:
class Dog extends Animal
{
}
let dog = new Dog(4);
alert(dog.Describe());
Notice how cool this is - we now have a Dog class, but by default, we don't need to do any work on it, because it will automatically inherit all fields and methods from the base class (Animal) and thereby all the functionality.
Overriding methods
But of course, it would make sense to modify the Dog class a bit. We can do this by overriding fields and/or methods, to give the Dog class more unique features, while still inheriting the default behavior from the Animal class. Let me show you how we can do it:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm sort of animal and I have " + this.numberOfLegs + " legs...";
}
}
class Dog extends Animal
{
constructor()
{
super(4);
}
Describe()
{
return "I'm a dog and I have " + this.numberOfLegs + " legs...";
}
}
let dog = new Dog();
alert(dog.Describe());
I do a couple of interesting things here. First of all, notice that I have created a new constructor, without the "numberOfLegs" parameter. In our system, dogs always have 4 legs, so we don't need to specify this each time we want to instantiate a Dog. Instead, our Dog class simply calls the base constructor (by using the super keyword) on the Animal class, passing in the value 4 for the numberOfLegs parameter.
I have also overridden the Describe() method to be less generic - it now tells that it's a dog and not just some kind of animal.
The super keyword
I just want to tell you a little bit more about the super keyword, because it's very important when it comes to inheritance. As you can see from the example above, it can be used to call the constructor on the base class, which allowed us to simplify the constructor of the Dog class, while still retaining the functionality of the constructor on the Animal class.
But obviously it doesn't only work for constructors - it works for all types of methods. Again, this will allow us to modify the behavior we inherit from the base class, while still retaining the functionality. In our example, we could modify the Describe() methods so that we could re-use most of the functionality from the Describe() method on the Animal class, for instance like this:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm an animal with " + this.numberOfLegs + " legs...";
}
}
class Dog extends Animal
{
constructor()
{
super(4);
}
Describe()
{
return super.Describe() + " Woof woof!";
}
}
let dog = new Dog();
// I'm an animal with 4 legs... Woof woof!
alert(dog.Describe());
I now re-use the functionality of the Describe() method found on the Animal class, while adding to it, to make the the behavior of the Describe() method unique for the Dog class but without having to re-write the existing code.
This is a great example of the power of the super keyword, and as you can see, there are several ways of using it:
- super() - will call the constructor on the base class
- super.method() - will call the designated method on the base class
- super.field - will reference the designated field on the base class
Overriding fields
Both methods and fields can be overridden - it's simply a matter of adding a member with the same name to the child class. When doing this, the behavior is now specific to the child class, but it's still visible to the parent class. I would like to prove this to you with this next example, where we override the numberOfLegs field instead of using the previously shown constructor trick, to always create dogs with 4 legs:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm an animal with " + this.numberOfLegs + " legs...";
}
}
class Dog extends Animal
{
numberOfLegs = 4;
Describe()
{
return super.Describe() + " Woof woof!";
}
}
let dog = new Dog();
// I'm an animal with 4 legs... Woof woof!
alert(dog.Describe());
Now our Dog class simply overrides the numberOfLegs field, to always have 4 legs, but notice that when we call the super.Describe() method, which uses this field, the value of the overridden numberOfLegs field is of course reflected here as well.
Extending existing classes/objects
Another really cool thing you can do with the extends keyword is to extend the functionality of built-in classes/objects. Since JavaScript comes with some very useful built-in objects, as described elsewhere in this tutorial, this will allow you to build upon these existing objects and add the functionality you need, while re-using the existing functionality.
As an example of this, let's try creating our own Array class, which extends the built-in Array object, to add a couple of methods. Just for fun, we'll call it AwesomeArray, and it will have a couple of semi-useful methods:
class AwesomeArray extends Array
{
isEmpty()
{
return this.length <= 0;
}
hasCat()
{
return this.includes("cat");
}
}
let array = new AwesomeArray();
// true
alert("Empty: " + array.isEmpty());
array.push("dog");
// false
alert("Has cat: " + array.hasCat());
array.push("cat");
// true
alert("Has cat: " + array.hasCat());
// dog,cat
alert(array);
array.sort();
// cat,dog
alert(array);
As you can see, extending a built-in class/object is just like extending your own classes - the syntax is identical. We then add a couple of very simple, and somewhat silly, methods, to check whether the array is empty and if it contains any cats.
As you can see, by using the this keyword, we are able to use functionality already found on the Array object, like the length property and the includes() method.
After we have declared our AwesomeArray class, you'll see how we can instantiate it like any other class, and use our own, custom methods from it, as well as the methods inherited from the Array object like push() and sort().
Summary
Inheritance allows you to build upon your own classes, or built-in classes/objects, and extend the functionality, without altering the existing class/object. Its one of the most fundamental and important aspects of objected oriented programming, and as soon as you start working more intensely with classes, you will find it incredible useful.