Appearance
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 Shepherd3. 方法重写
子类可以重写父类的方法,以提供自己的实现:
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); // 输出: Max3. 继承静态属性
静态属性也会被继承:
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 constructor4. 继承内置对象的注意事项
继承内置对象时,需要注意其特殊行为:
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 关键字实现,基于原型继承机制。子类可以继承父类的属性和方法,重写父类的方法,以及调用父类的方法。继承是面向对象编程中的重要概念,它可以帮助我们组织代码结构,提高代码的可重用性。然而,在使用继承时,应该注意合理设计继承层次,避免深层继承导致的复杂性,并且在适当的情况下考虑使用组合代替继承。