Skip to content

Super Keyword: The Bridge Connecting Subclasses and Parent Classes

When you're building a high-rise and need to continue construction on an existing foundation, you don't start from scratch. Instead, you leverage the existing foundation structure and then add new floors and features. In JavaScript class inheritance, the super keyword plays this role—it allows subclasses to call and utilize parent class functionality.

super is an important keyword introduced in ES6. It serves as both a communication bridge between subclasses and parent classes and the core mechanism for achieving code reuse and functionality extension. Mastering the use of super means you can build more elegant and efficient object-oriented programs.

Two Forms of Super Usage

The super keyword has two main usage forms: as a function call and as an object usage.

1. Super as a Function Call

When super is used as a function, it's used to call the parent class constructor:

javascript
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
    console.log(`Animal constructor: created a ${species}, named ${name}`);
  }

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

class Dog extends Animal {
  constructor(name, breed) {
    // Call parent class constructor
    super(name, "canine");
    this.breed = breed;
    console.log(`Dog constructor: breed is ${breed}`);
  }

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

const myDog = new Dog("Buddy", "Golden Retriever");
// Output:
// "Animal constructor: created a canine, named Buddy"
// "Dog constructor: breed is Golden Retriever"

console.log(myDog);
// Dog {
//   name: "Buddy",
//   species: "canine",
//   breed: "Golden Retriever"
// }

Key Rule: In a subclass constructor, super() must be called before using the this keyword:

javascript
class WrongExample extends Animal {
  constructor(name, breed) {
    this.breed = breed; // ❌ Error: Using this before super()
    super(name, "canine");
  }
}

class CorrectExample extends Animal {
  constructor(name, breed) {
    super(name, "canine"); // ✅ Correct: Call super() first
    this.breed = breed; // Then use this
  }
}

2. Super as an Object Usage

When super is used as an object, it can be used to call parent class methods:

javascript
class Vehicle {
  constructor(speed) {
    this.speed = speed;
  }

  move() {
    console.log(`Vehicle moves at ${this.speed} km/h`);
  }

  stop() {
    console.log("Vehicle stopped");
    this.speed = 0;
  }

  getInfo() {
    return `Speed: ${this.speed} km/h`;
  }
}

class Car extends Vehicle {
  constructor(speed, brand) {
    super(speed);
    this.brand = brand;
  }

  move() {
    // Call parent method and add subclass-specific logic
    console.log(`${this.brand} car started`);
    super.move(); // Call parent class move method
  }

  getInfo() {
    // Extend parent method
    const baseInfo = super.getInfo(); // Call parent getInfo
    return `${this.brand} Car - ${baseInfo}`;
  }

  stop() {
    // Completely override parent method
    console.log(`${this.brand} car braking`);
    super.stop(); // Still call parent stop logic
    console.log("Engine turned off");
  }
}

const myCar = new Car(120, "Toyota");
myCar.move();
// "Toyota car started"
// "Vehicle moves at 120 km/h"

console.log(myCar.getInfo());
// "Toyota Car - Speed: 120 km/h"

myCar.stop();
// "Toyota car braking"
// "Vehicle stopped"
// "Engine turned off"

Super Usage in Static Methods

super can also be used in static methods, where it calls parent class static methods:

javascript
class Database {
  static connection = null;

  static connect(config) {
    this.connection = `Connected to database: ${config.host}`;
    console.log(this.connection);
    return this.connection;
  }

  static disconnect() {
    this.connection = null;
    console.log("Database connection closed");
  }

  static getConnectionInfo() {
    return this.connection || "Not connected";
  }
}

class MySQLDatabase extends Database {
  static connect(config) {
    console.log("Initializing MySQL connection...");
    const result = super.connect(config); // Call parent static method
    console.log("MySQL connection configuration complete");
    return result;
  }

  static getConnectionInfo() {
    const baseInfo = super.getConnectionInfo(); // Call parent static method
    return `MySQL - ${baseInfo}`;
  }

  static executeQuery(sql) {
    if (!this.connection) {
      console.log("Please connect to database first");
      return;
    }
    console.log(`Execute SQL: ${sql}`);
  }
}

MySQLDatabase.connect({ host: "localhost", port: 3306 });
// "Initializing MySQL connection..."
// "Connected to database: localhost"
// "MySQL connection configuration complete"

console.log(MySQLDatabase.getConnectionInfo());
// "MySQL - Connected to database: localhost"

MySQLDatabase.executeQuery("SELECT * FROM users");
// "Execute SQL: SELECT * FROM users"

Practical Application Scenarios of Super

1. Method Enhancement in UI Components

javascript
class UIComponent {
  constructor(element, options = {}) {
    this.element = element;
    this.options = { ...this.getDefaultOptions(), ...options };
    this.isRendered = false;
    this.eventListeners = new Map();
  }

  getDefaultOptions() {
    return {
      visible: true,
      disabled: false,
      className: "",
      styles: {},
    };
  }

  render() {
    // Basic rendering logic
    this.element.style.display = this.options.visible ? "block" : "none";
    this.element.disabled = this.options.disabled;
    this.element.className = this.options.className;

    // Apply styles
    Object.assign(this.element.style, this.options.styles);

    this.isRendered = true;
    console.log("Basic component rendering complete");
  }

  bindEvents() {
    // Basic event binding
    if (this.options.disabled) {
      this.element.style.opacity = "0.6";
      this.element.style.pointerEvents = "none";
    }
  }

  show() {
    this.options.visible = true;
    this.element.style.display = "block";
  }

  hide() {
    this.options.visible = false;
    this.element.style.display = "none";
  }

  enable() {
    this.options.disabled = false;
    this.element.disabled = false;
    this.element.style.opacity = "1";
    this.element.style.pointerEvents = "auto";
  }

  disable() {
    this.options.disabled = true;
    this.element.disabled = true;
    this.element.style.opacity = "0.6";
    this.element.style.pointerEvents = "none";
  }

  destroy() {
    // Clean up event listeners
    this.eventListeners.forEach((handler, event) => {
      this.element.removeEventListener(event, handler);
    });
    this.eventListeners.clear();
    console.log("Basic component destroyed");
  }
}

class Modal extends UIComponent {
  getDefaultOptions() {
    return {
      ...super.getDefaultOptions(), // Call parent method to get default options
      title: "Modal",
      content: "",
      width: "auto",
      height: "auto",
      closable: true,
      showOverlay: true,
    };
  }

  render() {
    // Call parent basic rendering
    super.render();

    // Create modal structure
    this.createOverlay();
    this.createModalContent();

    console.log("Modal rendering complete");
  }

  createOverlay() {
    if (this.options.showOverlay) {
      this.overlay = document.createElement("div");
      this.overlay.className = "modal-overlay";
      this.overlay.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        z-index: 1000;
      `;
      document.body.appendChild(this.overlay);
    }
  }

  createModalContent() {
    this.element.innerHTML = `
      <div class="modal-header">
        <h3>${this.options.title}</h3>
        ${
          this.options.closable
            ? '<button class="modal-close">&times;</button>'
            : ""
        }
      </div>
      <div class="modal-body">
        ${this.options.content}
      </div>
    `;

    // Set modal styles
    Object.assign(this.element.style, {
      position: "fixed",
      top: "50%",
      left: "50%",
      transform: "translate(-50%, -50%)",
      width: this.options.width,
      height: this.options.height,
      background: "white",
      borderRadius: "8px",
      boxShadow: "0 4px 20px rgba(0, 0, 0, 0.3)",
      zIndex: "1001",
    });
  }

  bindEvents() {
    // Call parent basic event binding
    super.bindEvents();

    // Add modal-specific events
    if (this.options.closable) {
      const closeBtn = this.element.querySelector(".modal-close");
      const closeHandler = () => this.hide();

      closeBtn.addEventListener("click", closeHandler);
      this.eventListeners.set("close", closeHandler);

      if (this.overlay) {
        const overlayHandler = () => this.hide();
        this.overlay.addEventListener("click", overlayHandler);
        this.eventListeners.set("overlay", overlayHandler);
      }

      // ESC key close
      const keyHandler = (e) => {
        if (e.key === "Escape") {
          this.hide();
        }
      };
      document.addEventListener("keydown", keyHandler);
      this.eventListeners.set("keydown", keyHandler);
    }
  }

  show() {
    super.show(); // Call parent show method
    if (this.overlay) {
      this.overlay.style.display = "block";
    }
    // Disable background scrolling
    document.body.style.overflow = "hidden";
  }

  hide() {
    super.hide(); // Call parent hide method
    if (this.overlay) {
      this.overlay.style.display = "none";
    }
    // Restore background scrolling
    document.body.style.overflow = "auto";
  }

  setContent(content) {
    this.options.content = content;
    const body = this.element.querySelector(".modal-body");
    if (body) {
      body.innerHTML = content;
    }
  }

  setTitle(title) {
    this.options.title = title;
    const titleElement = this.element.querySelector(".modal-header h3");
    if (titleElement) {
      titleElement.textContent = title;
    }
  }

  destroy() {
    if (this.overlay && this.overlay.parentNode) {
      this.overlay.parentNode.removeChild(this.overlay);
    }
    // Call parent destroy method
    super.destroy();
    console.log("Modal destroyed");
  }
}

// Usage example
const modal = new Modal(document.createElement("div"), {
  title: "Confirm Delete",
  content:
    "Are you sure you want to delete this item? This action cannot be undone.",
  width: "400px",
});

document.body.appendChild(modal.element);
modal.render();
modal.show();

2. Method Inheritance in Data Models

javascript
class BaseModel {
  constructor(data = {}) {
    this.id = data.id || this.generateId();
    this.createdAt = data.createdAt || new Date();
    this.updatedAt = data.updatedAt || new Date();
    this.isDeleted = false;

    this.validateAndAssign(data);
  }

  generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
  }

  validateAndAssign(data) {
    Object.keys(data).forEach((key) => {
      if (this.isValidProperty(key, data[key])) {
        this[key] = data[key];
      }
    });
  }

  isValidProperty(key, value) {
    // Basic validation logic
    return key !== "id" && key !== "createdAt" && key !== "updatedAt";
  }

  update(data) {
    const oldData = { ...this };

    this.validateAndAssign(data);
    this.updatedAt = new Date();

    console.log(`Update record ${this.id}:`, {
      before: oldData,
      after: this.toJSON(),
    });

    return this.save();
  }

  save() {
    console.log("Save data:", this.toJSON());
    return Promise.resolve(this);
  }

  delete() {
    this.isDeleted = true;
    this.deletedAt = new Date();
    console.log("Delete record:", this.id);
    return Promise.resolve(true);
  }

  restore() {
    this.isDeleted = false;
    delete this.deletedAt;
    console.log("Restore record:", this.id);
    return this.save();
  }

  toJSON() {
    const result = {
      id: this.id,
      createdAt: this.createdAt,
      updatedAt: this.updatedAt,
    };

    if (this.isDeleted) {
      result.deletedAt = this.deletedAt;
    }

    return result;
  }

  static find(criteria = {}) {
    console.log("Find records:", criteria);
    return Promise.resolve([]);
  }

  static findOne(criteria = {}) {
    console.log("Find single record:", criteria);
    return Promise.resolve(null);
  }
}

class UserModel extends BaseModel {
  constructor(data = {}) {
    super(data);
    this.role = data.role || "user";
    this.loginCount = data.loginCount || 0;
    this.lastLoginAt = data.lastLoginAt || null;
  }

  isValidProperty(key, value) {
    // Call parent validation
    const baseValid = super.isValidProperty(key, value);

    // User model special validation
    const userValidations = {
      email: (val) => typeof val === "string" && val.includes("@"),
      username: (val) => typeof val === "string" && val.length >= 3,
      age: (val) => Number.isInteger(val) && val >= 0 && val <= 150,
      role: (val) => ["user", "admin", "moderator"].includes(val),
    };

    return baseValid && (!userValidations[key] || userValidations[key](value));
  }

  login() {
    this.lastLoginAt = new Date();
    this.loginCount++;
    this.updatedAt = new Date();

    console.log(`User ${this.username} logged in, ${this.loginCount}th time`);
    return this.save();
  }

  logout() {
    console.log(`User ${this.username} logged out`);
    return Promise.resolve(this);
  }

  changeRole(newRole) {
    const oldRole = this.role;
    this.role = newRole;
    this.updatedAt = new Date();

    console.log(
      `User ${this.username} role changed from ${oldRole} to ${newRole}`
    );
    return this.save();
  }

  toJSON() {
    // Call parent method to get basic data
    const baseJSON = super.toJSON();

    return {
      ...baseJSON,
      username: this.username,
      email: this.email,
      role: this.role,
      loginCount: this.loginCount,
      lastLoginAt: this.lastLoginAt,
    };
  }

  static findByEmail(email) {
    console.log("Find user by email:", email);
    return Promise.resolve(null);
  }

  static authenticate(email, password) {
    console.log("User authentication:", email);
    return Promise.resolve(null);
  }
}

class AdminModel extends UserModel {
  constructor(data = {}) {
    super(data);
    this.role = "admin";
    this.permissions = data.permissions || [];
    this.adminLevel = data.adminLevel || 1;
  }

  isValidProperty(key, value) {
    // Call parent validation
    const userValid = super.isValidProperty(key, value);

    // Admin special validation
    const adminValidations = {
      permissions: (val) => Array.isArray(val),
      adminLevel: (val) => Number.isInteger(val) && val >= 1 && val <= 5,
    };

    return (
      userValid && (!adminValidations[key] || adminValidations[key](value))
    );
  }

  grantPermission(permission) {
    if (!this.hasPermission(permission)) {
      this.permissions.push(permission);
      this.updatedAt = new Date();

      console.log(`Admin ${this.username} granted permission: ${permission}`);
      return this.save();
    }
    return Promise.resolve(this);
  }

  revokePermission(permission) {
    const index = this.permissions.indexOf(permission);
    if (index > -1) {
      this.permissions.splice(index, 1);
      this.updatedAt = new Date();

      console.log(`Admin ${this.username} revoked permission: ${permission}`);
      return this.save();
    }
    return Promise.resolve(this);
  }

  hasPermission(permission) {
    return this.permissions.includes(permission);
  }

  upgradeLevel(newLevel) {
    const oldLevel = this.adminLevel;
    this.adminLevel = Math.min(5, Math.max(1, newLevel));
    this.updatedAt = new Date();

    console.log(
      `Admin ${this.username} level upgraded from ${oldLevel} to ${this.adminLevel}`
    );
    return this.save();
  }

  toJSON() {
    // Call parent method
    const userJSON = super.toJSON();

    return {
      ...userJSON,
      adminLevel: this.adminLevel,
      permissions: [...this.permissions], // Copy array to avoid external modification
    };
  }

  static findAdmins() {
    console.log("Find all admins");
    return Promise.resolve([]);
  }
}

// Usage example
const admin = new AdminModel({
  username: "admin",
  email: "[email protected]",
  permissions: ["read", "write"],
  adminLevel: 3,
});

admin.grantPermission("delete").then(() => {
  console.log(admin.toJSON());
});

Common Pitfalls and Best Practices of Super

1. Avoid Using super in Arrow Functions

javascript
class Parent {
  method() {
    console.log("Parent method");
  }
}

class Child extends Parent {
  constructor() {
    super();

    // ❌ Error: super in arrow functions doesn't bind to class prototype
    this.arrowMethod = () => {
      super.method(); // ReferenceError: super is not defined
    };

    // ✅ Correct: super in regular methods works normally
    this.normalMethod = function () {
      // this.constructor.prototype.__proto__.method.call(this);
      // But this approach is not recommended, better to use class methods
    };
  }

  // ✅ Correct: super in class methods
  instanceMethod() {
    super.method(); // Works normally
  }
}

2. Pay Attention to this Binding in super

javascript
class Parent {
  constructor() {
    this.name = "Parent";
  }

  getName() {
    return this.name;
  }
}

class Child extends Parent {
  constructor() {
    super();
    this.name = "Child";
  }

  getParentName() {
    // this in super points to current subclass instance
    return super.getName(); // Returns 'Child', not 'Parent'
  }

  callParentMethodDirectly() {
    // Directly call parent method, but this still points to subclass instance
    return Parent.prototype.getName.call(this); // Returns 'Child'
  }
}

const child = new Child();
console.log(child.getParentName()); // "Child"
console.log(child.callParentMethodDirectly()); // "Child"

3. Don't Access Instance Properties in Static Methods

javascript
class Parent {
  constructor() {
    this.instanceProperty = "instance";
  }

  static staticMethod() {
    // ❌ Error: Cannot access instance properties in static methods
    console.log(this.instanceProperty); // undefined
  }
}

class Child extends Parent {
  static childStaticMethod() {
    // ❌ Error: super in static methods cannot access instance properties
    console.log(super.instanceProperty); // undefined
  }
}

4. Handle Super Call Return Values Correctly

javascript
class Parent {
  calculate(a, b) {
    return a + b;
  }
}

class Child extends Parent {
  calculate(a, b, c) {
    const parentResult = super.calculate(a, b); // Save parent method return value
    return parentResult * c; // Calculate based on parent result
  }
}

const child = new Child();
console.log(child.calculate(2, 3, 4)); // 20 ( (2+3) * 4 )

Relationship Between Super and Prototype Chain

Understanding how super works helps to use it better:

javascript
class Parent {
  static staticMethod() {
    return "Parent static";
  }

  instanceMethod() {
    return "Parent instance";
  }
}

class Child extends Parent {
  static staticMethod() {
    return super.staticMethod() + " -> Child static";
  }

  instanceMethod() {
    return super.instanceMethod() + " -> Child instance";
  }
}

// Behavior of super in static methods
console.log(Child.staticMethod());
// Equivalent to: Object.getPrototypeOf(Child).staticMethod.call(this)

// Behavior of super in instance methods
const child = new Child();
console.log(child.instanceMethod());
// Equivalent to: Object.getPrototypeOf(Child.prototype).instanceMethod.call(this)

console.log(Object.getPrototypeOf(Child) === Parent); // true
console.log(Object.getPrototypeOf(Child.prototype) === Parent.prototype); // true

Summary

The super keyword is an indispensable important tool in JavaScript's class inheritance system. By mastering these concepts and techniques, you can:

  1. Correctly call parent class constructors: Use super() to initialize inherited properties
  2. Reuse parent class methods: Call and extend existing functionality through super.method()
  3. Handle static method inheritance: Correctly use super in static contexts
  4. Avoid common pitfalls: Understand the working mechanisms and limitations of super

The use of super is not just syntactic sugar; it embodies the core ideas of "inheritance" and "extension" in object-oriented programming. Reasonable use of super can make your code more modular, maintainable, and effectively reuse existing code.

In actual development, always remember: super is the bridge connecting subclasses and parent classes. It makes inheritance relationships clearer and more powerful. Mastering the use of super means you've mastered the essence of JavaScript object-oriented programming.