JavaScript super Keyword: Calling Superclass Constructors

In JavaScript, when working with classes and inheritance, the super keyword plays a vital role. Specifically, when you create a subclass using the extends keyword, the super keyword is essential for calling the constructor of the parent class (also known as the superclass). This ensures that the parent class’s initialization logic is executed before the subclass’s own constructor logic. This article will delve into the purpose, syntax, and practical applications of the super keyword within the context of class constructors.

Understanding the Need for super()

When a subclass extends a superclass, it inherits all the properties and methods of the superclass. However, if the superclass has a constructor, it’s imperative to call that constructor from the subclass’s constructor to properly initialize the inherited properties. The super() keyword is the mechanism to achieve this.

Key Points:

  • The super() method must be called before accessing this in the constructor of a subclass. ⚠️
  • If you don’t call super() in the constructor of a subclass, JavaScript will throw an error.
  • You can pass arguments to super() which will then be passed to the superclass constructor.

Syntax of the super() Keyword

The basic syntax of the super() keyword within a subclass constructor is as follows:

class SuperClass {
    constructor(param1, param2) {
       // initialization logic
    }
}

class SubClass extends SuperClass {
    constructor(param1, param2, param3) {
        super(param1, param2);  // Calling Superclass constructor
        // subclass-specific initialization
    }
}

Here, super(param1, param2) is calling the constructor of the SuperClass passing the params to initialize the SuperClass properties.

Using super() with Parameters

Let’s look at examples with and without parameters. The super() keyword allows passing parameters to the parent class constructor. If a superclass constructor accepts arguments, you must pass those arguments when calling super().

Example 1: Superclass constructor with no parameters

class Animal {
    constructor() {
        this.alive = true;
        console.log("Animal is created");
    }
    speak() {
       console.log("Generic Animal Sound");
    }
}

class Dog extends Animal {
    constructor(name) {
        super(); // No parameters required to call parent constructor
        this.name = name;
        console.log(`Dog ${name} is created`);
    }
    speak() {
        console.log("Woof!");
    }
}

const dog1 = new Dog("Buddy");
dog1.speak();
console.log(dog1.alive)

Output:

Animal is created
Dog Buddy is created
Woof!
true

In this case, the Animal constructor doesn’t require parameters. So we call super() without any arguments in the Dog constructor. It can be seen the Animal constructor is called first, then the Dog constructor.

Example 2: Superclass constructor with parameters

class Shape {
    constructor(color, width) {
        this.color = color;
        this.width = width;
        console.log(`Shape with color ${color} and width ${width} created`);
    }
    describe() {
        console.log(`This is a shape with color: ${this.color} and width: ${this.width}`);
    }
}

class Rectangle extends Shape {
    constructor(color, width, height) {
       super(color, width); // Passing parameters to the Shape constructor
       this.height = height;
        console.log(`Rectangle with color ${color}, width ${width} and height ${height} created`);
    }
    describe() {
        console.log(`This is a rectangle with color: ${this.color}, width: ${this.width}, and height: ${this.height}`);
    }
}

const rectangle1 = new Rectangle("blue", 10, 20);
rectangle1.describe()

Output:

Shape with color blue and width 10 created
Rectangle with color blue, width 10 and height 20 created
This is a rectangle with color: blue, width: 10, and height: 20

Here, the Shape constructor requires parameters for color and width. The Rectangle constructor passes these parameters when calling super(color, width), ensuring the Shape class initializes these properties before the Rectangle class sets up the height property.

Importance of Calling super() First

The super() call must be the first statement in the constructor of the subclass when you are inheriting from a superclass having a constructor. This is because the subclass needs the superclass to be properly set up before it can modify or extend that behavior. JavaScript enforces this rule strictly, and if you attempt to access this before calling super(), an error will be thrown.

Example Showing the error when calling this before super()

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

class SubClassExample extends SuperClassExample {
    constructor(name, age) {
      //  this.age = age;  // This will throw a reference error as you cannot use this before calling super()
        super(name);
        this.age = age;
    }
}

try{
    const subClassEx = new SubClassExample("John", 30);
}
catch (e){
    console.error(e);
}

Output:

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

The error message explicitly indicates that you cannot access this or return from the constructor before calling super(). This highlights the critical order of operations within a subclass constructor.

Practical Use Cases

Let’s explore a more practical use case of the super keyword within the context of rendering different shapes on a HTML Canvas.

Example: Canvas Shape Rendering with Inheritance

<canvas
  id="canvasShapes"
  width="300"
  height="200"
  style="border: 1px solid black;"
></canvas>

<script>
  class ShapeCanvas {
    constructor(x, y, color) {
        this.x = x;
      this.y = y;
      this.color = color;
    }
    draw(ctx) {
      // Placeholder
    }
  }

  class CircleCanvas extends ShapeCanvas {
    constructor(x, y, color, radius) {
        super(x, y, color);
      this.radius = radius;
    }
      draw(ctx) {
      ctx.beginPath();
          ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
      ctx.fillStyle = this.color;
      ctx.fill();
    }
  }

  class RectangleCanvas extends ShapeCanvas {
    constructor(x, y, color, width, height) {
      super(x, y, color);
      this.width = width;
      this.height = height;
    }

    draw(ctx) {
      ctx.fillStyle = this.color;
      ctx.fillRect(this.x, this.y, this.width, this.height);
    }
  }

  const canvas_shapes = document.getElementById("canvasShapes");
  const ctx_shapes = canvas_shapes.getContext("2d");

  const circle = new CircleCanvas(50, 50, "red", 30);
  circle.draw(ctx_shapes);

  const rectangle = new RectangleCanvas(150, 50, "blue", 80, 60);
  rectangle.draw(ctx_shapes);
</script>

In this example, ShapeCanvas is the parent class with common properties such as x, y position and color. The CircleCanvas and RectangleCanvas classes inherit from ShapeCanvas. The super() keyword is used to call the ShapeCanvas constructor to set the base properties.

super in Method Overriding

Apart from calling superclass constructors, the super keyword can also be used to call superclass methods from overridden methods in subclasses, but that will be discussed in detail in future articles.

Conclusion

The super keyword in JavaScript classes is crucial for managing inheritance and ensuring the proper initialization of objects. By calling the parent class constructor using super(), subclasses inherit and initialize the parent’s properties and methods correctly. Always remember to call super() before accessing this within a subclass constructor to avoid runtime errors. This understanding is essential for building robust and well-structured object-oriented JavaScript applications.