Skip to content

Super 关键字:连接子类与父类的桥梁

当你在建造一座大楼,需要在现有地基上继续施工时,你不会完全从零开始,而是会利用已有的基础结构,然后在此基础上添加新的楼层和功能。在 JavaScript 的类继承中,super 关键字就扮演着这样的角色——它让子类能够调用和利用父类的功能。

super 是 ES6 引入的一个重要关键字,它既是子类与父类之间的通信桥梁,也是实现代码复用和功能扩展的核心机制。掌握 super 的使用,意味着你能够构建更加优雅和高效的面向对象程序。

Super 的两种使用形式

super 关键字有两种主要的使用形式:作为函数调用和作为对象使用。

1. Super 作为函数调用

super 作为函数使用时,它用于调用父类的构造函数:

javascript
class Animal {
  constructor(name, species) {
    this.name = name;
    this.species = species;
    console.log(`Animal 构造函数:创建了一只 ${species},名字叫 ${name}`);
  }

  eat() {
    console.log(`${this.name} 正在吃东西`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    // 调用父类构造函数
    super(name, "犬类");
    this.breed = breed;
    console.log(`Dog 构造函数:品种是 ${breed}`);
  }

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

const myDog = new Dog("旺财", "金毛");
// 输出:
// "Animal 构造函数:创建了一只 犬类,名字叫 旺财"
// "Dog 构造函数:品种是 金毛"

console.log(myDog);
// Dog {
//   name: "旺财",
//   species: "犬类",
//   breed: "金毛"
// }

关键规则:在子类构造函数中,super() 必须在使用 this 关键字之前调用:

javascript
class WrongExample extends Animal {
  constructor(name, breed) {
    this.breed = breed; // ❌ 错误:在 super() 之前使用 this
    super(name, "犬类");
  }
}

class CorrectExample extends Animal {
  constructor(name, breed) {
    super(name, "犬类"); // ✅ 正确:先调用 super()
    this.breed = breed; // 然后使用 this
  }
}

2. Super 作为对象使用

super 作为对象使用时,它可以用来调用父类的方法:

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

  move() {
    console.log(`交通工具以 ${this.speed} km/h 的速度移动`);
  }

  stop() {
    console.log("交通工具停止了");
    this.speed = 0;
  }

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

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

  move() {
    // 调用父类方法并添加子类特有的逻辑
    console.log(`${this.brand} 汽车启动了`);
    super.move(); // 调用父类的 move 方法
  }

  getInfo() {
    // 扩展父类方法
    const baseInfo = super.getInfo(); // 调用父类 getInfo
    return `${this.brand} 汽车 - ${baseInfo}`;
  }

  stop() {
    // 完全重写父类方法
    console.log(`${this.brand} 汽车刹车了`);
    super.stop(); // 仍然调用父类停止逻辑
    console.log("发动机熄火");
  }
}

const myCar = new Car(120, "Toyota");
myCar.move();
// "Toyota 汽车启动了"
// "交通工具以 120 km/h 的速度移动"

console.log(myCar.getInfo());
// "Toyota 汽车 - 速度:120 km/h"

myCar.stop();
// "Toyota 汽车刹车了"
// "交通工具停止了"
// "发动机熄火"

Super 在静态方法中的使用

super 也可以在静态方法中使用,此时它调用的是父类的静态方法:

javascript
class Database {
  static connection = null;

  static connect(config) {
    this.connection = `连接到数据库: ${config.host}`;
    console.log(this.connection);
    return this.connection;
  }

  static disconnect() {
    this.connection = null;
    console.log("数据库连接已断开");
  }

  static getConnectionInfo() {
    return this.connection || "未连接";
  }
}

class MySQLDatabase extends Database {
  static connect(config) {
    console.log("正在初始化 MySQL 连接...");
    const result = super.connect(config); // 调用父类静态方法
    console.log("MySQL 连接配置完成");
    return result;
  }

  static getConnectionInfo() {
    const baseInfo = super.getConnectionInfo(); // 调用父类静态方法
    return `MySQL - ${baseInfo}`;
  }

  static executeQuery(sql) {
    if (!this.connection) {
      console.log("请先连接数据库");
      return;
    }
    console.log(`执行 SQL: ${sql}`);
  }
}

MySQLDatabase.connect({ host: "localhost", port: 3306 });
// "正在初始化 MySQL 连接..."
// "连接到数据库: localhost"
// "MySQL 连接配置完成"

console.log(MySQLDatabase.getConnectionInfo());
// "MySQL - 连接到数据库: localhost"

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

Super 的实际应用场景

1. UI 组件中的方法增强

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() {
    // 基础渲染逻辑
    this.element.style.display = this.options.visible ? "block" : "none";
    this.element.disabled = this.options.disabled;
    this.element.className = this.options.className;

    // 应用样式
    Object.assign(this.element.style, this.options.styles);

    this.isRendered = true;
    console.log("基础组件渲染完成");
  }

  bindEvents() {
    // 基础事件绑定
    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() {
    // 清理事件监听器
    this.eventListeners.forEach((handler, event) => {
      this.element.removeEventListener(event, handler);
    });
    this.eventListeners.clear();
    console.log("基础组件已销毁");
  }
}

class Modal extends UIComponent {
  getDefaultOptions() {
    return {
      ...super.getDefaultOptions(), // 调用父类方法获取默认选项
      title: "模态框",
      content: "",
      width: "auto",
      height: "auto",
      closable: true,
      showOverlay: true,
    };
  }

  render() {
    // 调用父类基础渲染
    super.render();

    // 创建模态框结构
    this.createOverlay();
    this.createModalContent();

    console.log("模态框渲染完成");
  }

  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>
    `;

    // 设置模态框样式
    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() {
    // 调用父类基础事件绑定
    super.bindEvents();

    // 添加模态框特有的事件
    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 键关闭
      const keyHandler = (e) => {
        if (e.key === "Escape") {
          this.hide();
        }
      };
      document.addEventListener("keydown", keyHandler);
      this.eventListeners.set("keydown", keyHandler);
    }
  }

  show() {
    super.show(); // 调用父类显示方法
    if (this.overlay) {
      this.overlay.style.display = "block";
    }
    // 禁止背景滚动
    document.body.style.overflow = "hidden";
  }

  hide() {
    super.hide(); // 调用父类隐藏方法
    if (this.overlay) {
      this.overlay.style.display = "none";
    }
    // 恢复背景滚动
    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);
    }
    // 调用父类销毁方法
    super.destroy();
    console.log("模态框已销毁");
  }
}

// 使用示例
const modal = new Modal(document.createElement("div"), {
  title: "确认删除",
  content: "您确定要删除这个项目吗?此操作不可撤销。",
  width: "400px",
});

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

2. 数据模型中的方法继承

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) {
    // 基础验证逻辑
    return key !== "id" && key !== "createdAt" && key !== "updatedAt";
  }

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

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

    console.log(`更新记录 ${this.id}:`, {
      before: oldData,
      after: this.toJSON(),
    });

    return this.save();
  }

  save() {
    console.log("保存数据:", this.toJSON());
    return Promise.resolve(this);
  }

  delete() {
    this.isDeleted = true;
    this.deletedAt = new Date();
    console.log("删除记录:", this.id);
    return Promise.resolve(true);
  }

  restore() {
    this.isDeleted = false;
    delete this.deletedAt;
    console.log("恢复记录:", 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("查找记录:", criteria);
    return Promise.resolve([]);
  }

  static findOne(criteria = {}) {
    console.log("查找单个记录:", 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) {
    // 调用父类验证
    const baseValid = super.isValidProperty(key, value);

    // 用户模型的特殊验证
    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(`用户 ${this.username} 登录,第 ${this.loginCount} 次`);
    return this.save();
  }

  logout() {
    console.log(`用户 ${this.username} 登出`);
    return Promise.resolve(this);
  }

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

    console.log(`用户 ${this.username} 角色从 ${oldRole} 变更为 ${newRole}`);
    return this.save();
  }

  toJSON() {
    // 调用父类方法获取基础数据
    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("根据邮箱查找用户:", email);
    return Promise.resolve(null);
  }

  static authenticate(email, password) {
    console.log("用户认证:", 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) {
    // 调用父类验证
    const userValid = super.isValidProperty(key, value);

    // 管理员的特殊验证
    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(`管理员 ${this.username} 获得权限: ${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(`管理员 ${this.username} 失去权限: ${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(
      `管理员 ${this.username} 等级从 ${oldLevel} 升级到 ${this.adminLevel}`
    );
    return this.save();
  }

  toJSON() {
    // 调用父类方法
    const userJSON = super.toJSON();

    return {
      ...userJSON,
      adminLevel: this.adminLevel,
      permissions: [...this.permissions], // 复制数组避免外部修改
    };
  }

  static findAdmins() {
    console.log("查找所有管理员");
    return Promise.resolve([]);
  }
}

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

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

Super 的常见陷阱和最佳实践

1. 避免在箭头函数中使用 super

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

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

    // ❌ 错误:箭头函数中的 super 不绑定到类的原型
    this.arrowMethod = () => {
      super.method(); // ReferenceError: super is not defined
    };

    // ✅ 正确:普通方法中的 super 正常工作
    this.normalMethod = function () {
      // this.constructor.prototype.__proto__.method.call(this);
      // 但这种方式不推荐,建议使用类方法
    };
  }

  // ✅ 正确:类方法中的 super
  instanceMethod() {
    super.method(); // 正常工作
  }
}

2. 注意 super 中的 this 绑定

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

  getName() {
    return this.name;
  }
}

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

  getParentName() {
    // super 中的 this 指向当前子类实例
    return super.getName(); // 返回 'Child',而不是 'Parent'
  }

  callParentMethodDirectly() {
    // 直接调用父类方法,但 this 仍然指向子类实例
    return Parent.prototype.getName.call(this); // 返回 'Child'
  }
}

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

3. 不要在静态方法中访问实例属性

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

  static staticMethod() {
    // ❌ 错误:静态方法中无法访问实例属性
    console.log(this.instanceProperty); // undefined
  }
}

class Child extends Parent {
  static childStaticMethod() {
    // ❌ 错误:super 在静态方法中无法访问实例属性
    console.log(super.instanceProperty); // undefined
  }
}

4. 正确处理 super 调用的返回值

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

class Child extends Parent {
  calculate(a, b, c) {
    const parentResult = super.calculate(a, b); // 保存父类方法返回值
    return parentResult * c; // 在父类结果基础上进行计算
  }
}

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

Super 与原型链的关系

理解 super 的工作原理有助于更好地使用它:

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";
  }
}

// super 在静态方法中的行为
console.log(Child.staticMethod());
// 相当于:Object.getPrototypeOf(Child).staticMethod.call(this)

// super 在实例方法中的行为
const child = new Child();
console.log(child.instanceMethod());
// 相当于:Object.getPrototypeOf(Child.prototype).instanceMethod.call(this)

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

总结

super 关键字是 JavaScript 类继承体系中不可或缺的重要工具。通过掌握这些概念和技术,你可以:

  1. 正确调用父类构造函数:使用 super() 初始化继承的属性
  2. 重用父类方法:通过 super.method() 调用并扩展现有功能
  3. 处理静态方法继承:在静态上下文中正确使用 super
  4. 避免常见陷阱:理解 super 的工作机制和限制

super 的使用不仅仅是语法糖,它体现了面向对象编程中"继承"和"扩展"的核心思想。合理使用 super 可以让你的代码更加模块化、可维护,并且能够有效地复用现有代码。

在实际开发中,始终记住:super 是连接子类与父类的桥梁,它让继承关系变得更加清晰和强大。掌握好 super 的使用,你就掌握了 JavaScript 面向对象编程的精髓。