Implementing JavaScript inheritance using extends
and super
Prior to ES6, implementing a proper inheritance required multiple steps. One of the most commonly used strategies is the prototypal inheritance. The following illustrates how the Bird
inherits properties from the Animal
using the prototype inheritance technique.
function Animal(legs) {
this.legs = legs;
}
Animal.prototype.walk = function() {
console.log('walking on ' + this.legs + ' legs');
}
function Bird(legs) {
Animal.call(this, legs);
}
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Animal;
Bird.prototype.fly = function() {
console.log('flying');
}
var pigeon = new Bird(2);
pigeon.walk(); // walking on 2 legs
pigeon.fly(); // flying
ES6 simplified these steps by using the extends
and super
keywords. The following example defines the Animal
and Bird
classes and establishes the inheritance through the extends
and super
keywords.
class Animal {
constructor(legs) {
this.legs = legs;
}
walk() {
console.log('walking on ' + this.legs + ' legs');
}
}
class Bird extends Animal {
constructor(legs) {
super(legs);
}
fly() {
console.log('flying');
}
}
let bird = new Bird(2);
bird.walk();
bird.fly();
In this example, the Bird
‘s constructor uses super()
to call the Animal
‘s constructor with the specified arguments.
Note that the class version is just the syntactic sugar for the prototypal inheritance. In other words, JavaScript uses classes for the syntax but still realizes on the prototype mechanism
The Animal
class is called base class and the Bird
class is known as derived class. JavaScript requires the derived class to use super()
if it has a constructor. As you see in the Bird
class, the super(legs)
is equivalent to the following statement.
Animal.call(this, legs);
If you decide to not use constructor in the Bird
class, you can do it as follows:
class Bird extends Animal {
fly() {
console.log('flying');
}
}
It is equivalent to the following class:
class Bird extends Animal {
constructor(...args) {
super(...args);
}
fly() {
console.log('flying');
}
}
However, if you specify the constructor, you muse call super()
inside the constructor, therefore the following code results in an error.
class Bird extends Animal {
constructor(legs) {
}
fly() {
console.log('flying');
}
}
Because the super()
initializes the this
object, you must call the super()
before accessing the this
object. Trying to access this
before calling super()
results in an error.
For example, if you want to initialize the color
property of the Bird
class, you can do it as follows:
class Bird extends Animal {
constructor(legs, color) {
super(legs);
this.color = color;
}
fly() {
console.log('flying');
}
getColor() {
console.log(this.color);
}
}
let pegion = new Bird(2, 'white');
console.log(pegion.getColor());
Shadowing methods
JavaScript allows you to add a new method in the derived class that has the same name as a method in the base class. In this case, when you call the method of an object of the derived class, that method will shadow the method in the base class.
The following Dog
class extends the Animal
class and redefines the walk()
method.
class Dog extends Animal {
constructor() {
super(4);
}
walk() {
console.log(`go walking`);
}
}
let bingo = new Dog();
bingo.walk(); // go walking
To call the method of the base class in the derived class, you use super.method()
like this:
class Dog extends Animal {
constructor() {
super(4);
}
walk() {
super.walk();
console.log(`go walking`);
}
}
let bingo = new Dog();
bingo.walk();
// walking on 4 legs
// go walking
Inheriting static members
The derived class inherits all static members of the base class. See the following example.
class Animal {
constructor(legs) {
this.legs = legs;
}
walk() {
console.log('walking on ' + this.legs + ' legs');
}
static helloWorld() {
console.log('Hello World');
}
}
class Bird extends Animal {
fly() {
console.log('flying');
}
}
In this example, the Animal
class has the helloWorld()
static method and this method is available as Bird.helloWorld()
and behaves the same as the Animal.helloWorld()
method. See the following code:
Bird.helloWorld(); // Hello World
Inheriting from built-in types
JavaScript allows you to extend a built-in type such as Array, String, Map, and Set through inheritance. The following Queue
class extends the Array
reference type. The syntax is much cleaner than the Queue
implemented using the constructor/prototype pattern.
class Queue extends Array {
enqueue(e) {
super.push(e);
}
dequeue() {
return super.shift();
}
peek() {
return !this.empty() ? this[0] : undefined;
}
empty() {
return this.length === 0;
}
}
var customers = new Queue();
customers.enqueue('A');
customers.enqueue('B');
customers.enqueue('C');
while (!customers.empty()) {
console.log(customers.dequeue());
}
In this tutorial, you have learned how to implement JavaScript inheritance using the extends
and super
keywords.