Skip to content

JavaScript 类继承

继承的概念

继承是面向对象编程中的重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。在 JavaScript 中,类继承使用 extends 关键字实现,基于原型继承机制。

基本继承

1. 语法

使用 extends 关键字创建子类:

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

  speak() {
    return `${this.name} makes a noise.`;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类构造函数
    this.breed = breed;
  }

  bark() {
    return `${this.name} barks!`;
  }
}

const dog = new Dog("Rex", "German Shepherd");
console.log(dog.name); // 输出: Rex
console.log(dog.breed); // 输出: German Shepherd
console.log(dog.speak()); // 输出: Rex makes a noise.(继承自父类)
console.log(dog.bark()); // 输出: Rex barks!(子类特有)

2. 构造函数

子类的构造函数必须调用 super() 方法,以便初始化从父类继承的属性:

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

class Dog extends Animal {
  constructor(name, breed) {
    // 错误:在访问 this 之前必须调用 super()
    // this.breed = breed;

    // 正确:先调用 super()
    super(name);
    this.breed = breed;
  }
}

const dog = new Dog("Rex", "German Shepherd");
console.log(dog.name); // 输出: Rex
console.log(dog.breed); // 输出: German Shepherd

3. 方法重写

子类可以重写父类的方法,以提供自己的实现:

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

  speak() {
    return `${this.name} makes a noise.`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  // 重写父类的 speak 方法
  speak() {
    return `${this.name} barks!`;
  }
}

class Cat extends Animal {
  constructor(name) {
    super(name);
  }

  // 重写父类的 speak 方法
  speak() {
    return `${this.name} meows!`;
  }
}

const dog = new Dog("Rex");
console.log(dog.speak()); // 输出: Rex barks!(重写后的方法)

const cat = new Cat("Whiskers");
console.log(cat.speak()); // 输出: Whiskers meows!(重写后的方法)

const animal = new Animal("Generic");
console.log(animal.speak()); // 输出: Generic makes a noise.(父类的方法)

高级继承特性

1. 调用父类方法

子类可以使用 super 关键字调用父类的方法:

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

  speak() {
    return `${this.name} makes a noise.`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  speak() {
    const parentSpeak = super.speak(); // 调用父类的 speak 方法
    return `${parentSpeak} Specifically, ${this.name} barks!`;
  }
}

const dog = new Dog("Rex");
console.log(dog.speak()); // 输出: Rex makes a noise. Specifically, Rex barks!

2. 继承静态方法

静态方法也会被继承:

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

  static create(name) {
    return new Animal(name);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }

  static createWithBreed(name, breed) {
    return new Dog(name, breed);
  }
}

// 调用从父类继承的静态方法
const animal = Animal.create("Generic");
console.log(animal.name); // 输出: Generic

// 调用子类的静态方法
const dog = Dog.createWithBreed("Rex", "German Shepherd");
console.log(dog.name); // 输出: Rex
console.log(dog.breed); // 输出: German Shepherd

// 子类也可以调用从父类继承的静态方法
const dog2 = Dog.create("Max");
console.log(dog2.name); // 输出: Max

3. 继承静态属性

静态属性也会被继承:

javascript
class Animal {
  static species = "Animal";

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

class Dog extends Animal {
  static species = "Canis lupus familiaris";

  constructor(name) {
    super(name);
  }
}

console.log(Animal.species); // 输出: Animal
console.log(Dog.species); // 输出: Canis lupus familiaris(重写后的静态属性)

4. 多层继承

JavaScript 支持多层继承,即一个子类可以被另一个类继承:

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

  speak() {
    return `${this.name} makes a noise.`;
  }
}

class Mammal extends Animal {
  constructor(name, hasFur) {
    super(name);
    this.hasFur = hasFur;
  }

  isWarmBlooded() {
    return true;
  }
}

class Dog extends Mammal {
  constructor(name, breed) {
    super(name, true);
    this.breed = breed;
  }

  bark() {
    return `${this.name} barks!`;
  }
}

const dog = new Dog("Rex", "German Shepherd");
console.log(dog.name); // 输出: Rex
console.log(dog.hasFur); // 输出: true
console.log(dog.speak()); // 输出: Rex makes a noise.(继承自 Animal)
console.log(dog.isWarmBlooded()); // 输出: true(继承自 Mammal)
console.log(dog.bark()); // 输出: Rex barks!(Dog 特有)

5. 继承内置对象

JavaScript 类可以继承内置对象,如 Array、Object 等:

javascript
class MyArray extends Array {
  sum() {
    return this.reduce((total, num) => total + num, 0);
  }

  average() {
    if (this.length === 0) return 0;
    return this.sum() / this.length;
  }
}

const myArray = new MyArray(1, 2, 3, 4, 5);
console.log(myArray.sum()); // 输出: 15
console.log(myArray.average()); // 输出: 3
console.log(myArray.length); // 输出: 5(继承自 Array)
console.log(myArray.push(6)); // 输出: 6(继承自 Array)
console.log(myArray.sum()); // 输出: 21

继承与组合

1. 组合优于继承

在某些情况下,组合可能比继承更灵活:

javascript
// 组合方式
class CanSpeak {
  speak() {
    return `${this.name} makes a noise.`;
  }
}

class CanWalk {
  walk() {
    return `${this.name} walks.`;
  }
}

class Dog {
  constructor(name) {
    this.name = name;
    Object.assign(this, new CanSpeak(), new CanWalk());
  }

  bark() {
    return `${this.name} barks!`;
  }
}

const dog = new Dog("Rex");
console.log(dog.speak()); // 输出: Rex makes a noise.
console.log(dog.walk()); // 输出: Rex walks.
console.log(dog.bark()); // 输出: Rex barks!

2. 继承的适用场景

继承适用于以下场景:

  • 存在明确的 "is-a" 关系(如 Dog 是 Animal)
  • 子类需要继承父类的大部分属性和方法
  • 子类需要重写父类的部分方法

组合适用于以下场景:

  • 存在 "has-a" 关系(如 Dog 有 CanSpeak 能力)
  • 类需要灵活组合不同的功能
  • 避免深层继承导致的复杂性

继承的最佳实践

1. 合理使用继承

只在确实存在 "is-a" 关系时使用继承:

javascript
// 好的做法:明确的 is-a 关系
class Vehicle {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }

  drive() {
    return `${this.make} ${this.model} is driving.`;
  }
}

class Car extends Vehicle {
  constructor(make, model, doors) {
    super(make, model);
    this.doors = doors;
  }
}

// 不好的做法:没有明确的 is-a 关系
class Person {
  constructor(name) {
    this.name = name;
  }
}

class Student extends Person {
  constructor(name, grade) {
    super(name);
    this.grade = grade;
  }
}

2. 保持继承层次简洁

避免深层继承,通常不超过 2-3 层:

javascript
// 好的做法:简洁的继承层次
class Animal {
  // ...
}

class Dog extends Animal {
  // ...
}

// 不好的做法:深层继承
class LivingBeing {
  // ...
}

class Animal extends LivingBeing {
  // ...
}

class Mammal extends Animal {
  // ...
}

class Dog extends Mammal {
  // ...
}

3. 调用父类方法

在重写父类方法时,考虑是否需要调用父类的方法:

javascript
class Animal {
  speak() {
    return `${this.name} makes a noise.`;
  }
}

class Dog extends Animal {
  speak() {
    // 好的做法:调用父类方法
    const parentSpeak = super.speak();
    return `${parentSpeak} Specifically, ${this.name} barks!`;
  }
}

4. 使用抽象方法

在父类中定义抽象方法,要求子类实现:

javascript
class Animal {
  speak() {
    throw new Error("Subclass must implement speak()");
  }
}

class Dog extends Animal {
  speak() {
    return "Woof!";
  }
}

class Cat extends Animal {
  speak() {
    return "Meow!";
  }
}

const dog = new Dog();
console.log(dog.speak()); // 输出: Woof!

const cat = new Cat();
console.log(cat.speak()); // 输出: Meow!

5. 避免修改父类

子类不应该修改父类的属性或方法,而是应该通过重写或扩展来实现自己的功能:

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

  speak() {
    return `${this.name} makes a noise.`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
    // 不好的做法:修改父类的方法
    // this.speak = function() { return 'Woof!'; };
  }

  // 好的做法:重写父类的方法
  speak() {
    return "Woof!";
  }
}

继承的常见错误

1. 忘记调用 super()

在子类构造函数中,必须调用 super() 方法:

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

class Dog extends Animal {
  constructor(name, breed) {
    // 错误:忘记调用 super()
    // this.breed = breed; // 错误:Must call super constructor in derived class before accessing 'this' or returning from derived constructor

    // 正确:先调用 super()
    super(name);
    this.breed = breed;
  }
}

2. 在 super() 之前访问 this

在调用 super() 之前,不能访问 this

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

class Dog extends Animal {
  constructor(name, breed) {
    // 错误:在 super() 之前访问 this
    // this.breed = breed;
    // super(name);

    // 正确:先调用 super(),再访问 this
    super(name);
    this.breed = breed;
  }
}

3. 多层继承中的 super 调用

在多层继承中,super() 调用会沿着原型链向上传递:

javascript
class Grandparent {
  constructor(name) {
    this.name = name;
    console.log("Grandparent constructor");
  }
}

class Parent extends Grandparent {
  constructor(name, age) {
    super(name);
    this.age = age;
    console.log("Parent constructor");
  }
}

class Child extends Parent {
  constructor(name, age, grade) {
    super(name, age);
    this.grade = grade;
    console.log("Child constructor");
  }
}

const child = new Child("John", 30, 5);
// 输出:
// Grandparent constructor
// Parent constructor
// Child constructor

4. 继承内置对象的注意事项

继承内置对象时,需要注意其特殊行为:

javascript
class MyArray extends Array {
  // 注意:内置对象的构造函数行为可能不同
}

const arr = new MyArray(1, 2, 3);
console.log(arr.length); // 输出: 3

// 某些内置方法可能返回父类的实例
const sliced = arr.slice(0, 2);
console.log(sliced instanceof MyArray); // 输出: true(在现代浏览器中)

小结

JavaScript 类继承使用 extends 关键字实现,基于原型继承机制。子类可以继承父类的属性和方法,重写父类的方法,以及调用父类的方法。继承是面向对象编程中的重要概念,它可以帮助我们组织代码结构,提高代码的可重用性。然而,在使用继承时,应该注意合理设计继承层次,避免深层继承导致的复杂性,并且在适当的情况下考虑使用组合代替继承。