首页 / TypeScript 入门教程 / 继承与多态

TypeScript 入门教程

继承与多态

本教程共 80 篇 · 第 33 篇 · 更新于 2026-08-10 · 约 11 分钟阅读

TypeScriptTypeScript 入门教程继承多态override

本节目标:学会用 extends 继承类、用 super 调用父类、重写方法实现多态,并掌握 TypeScript 4.3 引入的 override 关键字。

如果你已经理解了接口的继承(第 25 章),类的继承思路差不多——子类能拿到父类的所有属性和方法,还能加自己的料。

extends:继承一个类

class Animal {
  name: string;

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

  move(): void {
    console.log(`${this.name} 在移动`);
  }
}

class Dog extends Animal {
  bark(): void {
    console.log(`${this.name} 汪汪汪!`);
  }
}

const dog = new Dog("旺财");
dog.move();  // "旺财 在移动" — 从 Animal 继承来的
dog.bark();  // "旺财 汪汪汪!" — Dog 自己的方法

JavaScript(以及 TypeScript)只支持单继承——一个类只能 extends 一个父类。如果需要组合多个来源的行为,后面的 Mixin 章节(第 37 章)会解决这个问题。

super:调用父类

在子类的构造函数里,必须先调用 super() 才能用 this

class Animal {
  name: string;

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

class Dog extends Animal {
  breed: string;

  constructor(name: string, breed: string) {
    super(name);        // 必须先调用父类构造函数
    this.breed = breed; // 然后才能初始化自己的属性
  }
}

忘记调 super() 是初学者的高频错误。好消息是 TypeScript 会帮你检查:

class Dog extends Animal {
  breed: string;

  constructor(name: string, breed: string) {
    // ❌ 报错:'super' must be called before accessing 'this'
    this.breed = breed;
    super(name);
  }
}

super 也可以用来调用父类的方法

class Animal {
  makeSound(): void {
    console.log("动物发出声音");
  }
}

class Cat extends Animal {
  makeSound(): void {
    super.makeSound();              // 先调用父类的
    console.log("其实是喵喵叫");     // 再加自己的
  }
}

const cat = new Cat();
cat.makeSound();
// 动物发出声音
// 其实是喵喵叫

方法重写(Override)

子类可以定义和父类同名的方法,这就是重写。前面 Cat.makeSound() 重写了 Animal.makeSound(),就是个典型的例子。

不过重写有个重要的约束:子类的方法必须兼容父类的方法签名。 TypeScript 强制要求派生类始终是基类的子类型。

什么意思?看个例子:

class Base {
  greet(): void {
    console.log("Hello!");
  }
}

class Derived extends Base {
  greet(name?: string): void {    // ✅ 可选参数,兼容无参调用
    if (name === undefined) {
      super.greet();
    } else {
      console.log(`Hello, ${name}!`);
    }
  }
}

const d: Derived = new Derived();
d.greet();         // "Hello!"
d.greet("Alice");  // "Hello, Alice!"

// 多态:父类型引用调用子类方法
const b: Base = d;
b.greet();         // "Hello!" —— 没问题

但如果子类把可选参数改成必填的会怎样?

class BadDerived extends Base {
  greet(name: string): void {     // ❌ 报错!
    console.log(`Hello, ${name}!`);
  }
}
// 报错原因:Base.greet() 可以无参调用,但 BadDerived.greet 现在要求必须有参数
// 如果有人通过 Base 类型的引用调用 greet(),传入 undefined 就会炸

TypeScript 用这个约束保护了多态的安全性。

override 关键字

从 TypeScript 4.3 开始,你可以用 override 关键字显式标记重写的方法:

class Base {
  greet(): void {
    console.log("Hello!");
  }
}

class Derived extends Base {
  override greet(): void {    // ✅ 明确说明这是在重写
    console.log("你好!");
  }
}

override 的好处不是让代码更好跑,而是防止意外。最常见的场景是:父类重构时改了方法名,但你忘了同步更新子类:

class Base {
  sayHello(): void {          // 重构:greet 改名为 sayHello
    console.log("Hello!");
  }
}

class Derived extends Base {
  // override greet(): void {  // ❌ 报错:Base 里已经没有 greet 了
  //   console.log("Hello!");
  // }

  override sayHello(): void {  // ✅ 正确,匹配上了
    console.log("Hello!");
  }
}

没有 override 的时候,上面的 greet() 会静默地变成一个全新的方法——你的 IDE 和编译器都不会提醒你。有了 override,拼写错误、重构遗漏都无处遁形。

Tip

你可以在 tsconfig.json 里设置 "noImplicitOverride": true,强制要求所有重写的方法都加 override。这能大幅降低大型项目中重构出错的风险。

多态:一个接口,多种实现

多态听起来唬人,本质就是一句话:父类型的引用可以指向子类的对象,调用方法时执行的是实际子类的实现。

class Animal {
  constructor(public name: string) {}

  speak(): void {
    console.log(`${this.name} 发出声音`);
  }
}

class Dog extends Animal {
  speak(): void {
    console.log(`${this.name} 汪汪汪!`);
  }
}

class Cat extends Animal {
  speak(): void {
    console.log(`${this.name} 喵喵喵!`);
  }
}

class Duck extends Animal {
  speak(): void {
    console.log(`${this.name} 嘎嘎嘎!`);
  }
}

// 多态:用 Animal 类型的数组存储各种子类
const animals: Animal[] = [
  new Dog("旺财"),
  new Cat("小花"),
  new Duck("唐老鸭"),
  new Animal("未知生物"),
];

// 同一行代码,不同对象产生不同行为
for (const animal of animals) {
  animal.speak();
}
// 旺财 汪汪汪!
// 小花 喵喵喵!
// 唐老鸭 嘎嘎嘎!
// 未知生物 发出声音

这里的 for 循环里,animal 的类型是 Animal,但实际调用时执行的是每个子类自己的 speak() 实现。这就是多态的核心价值:写代码时你只需要知道”这是一个 Animal”,不用关心具体是哪个子类。

多态的实际用途

多态最常见的应用是策略模式依赖反转。比如一个支付系统:

class Payment {
  constructor(public amount: number) {}

  process(): string {
    return `处理了 ¥${this.amount} 的支付`;
  }
}

class CreditCardPayment extends Payment {
  constructor(amount: number, private cardNumber: string) {
    super(amount);
  }

  override process(): string {
    return `信用卡 ${this.cardNumber.slice(-4)} 支付 ¥${this.amount}`;
  }
}

class WeChatPayment extends Payment {
  override process(): string {
    return `微信支付 ¥${this.amount}`;
  }
}

// 统一的结算函数——不需要知道具体是哪种支付方式
function checkout(payments: Payment[]): void {
  for (const p of payments) {
    console.log(p.process());
  }
}

checkout([
  new CreditCardPayment(100, "1234-5678-9012-3456"),
  new WeChatPayment(50),
]);
// 信用卡 3456 支付 ¥100
// 微信支付 ¥50
Note

由于 TypeScript 的类型系统是结构化的,多态不一定要通过 extends 来实现——只要一个对象有 process() 方法且签名兼容,就能传给 checkout()。这是 TypeScript 和 Java/C# 的显著区别。

instanceOf:运行时类型检查

有时候你确实需要知道一个对象是哪个具体类的实例,用 instanceof

class Circle {
  radius: number;
  constructor(radius: number) { this.radius = radius; }
  area(): number { return Math.PI * this.radius ** 2; }
}

class Rectangle {
  width: number;
  height: number;
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
  area(): number { return this.width * this.height; }
}

function describe(shape: Circle | Rectangle): string {
  if (shape instanceof Circle) {
    return `圆形,半径 ${shape.radius},面积 ${shape.area().toFixed(2)}`;
  } else {
    return `矩形,${shape.width} × ${shape.height},面积 ${shape.area()}`;
  }
}

instanceof 是运行时操作,检查原型的继承链。它不受 TypeScript 类型擦除的影响。

小结

  • extends 实现单继承,子类拿到父类的全部属性和方法。
  • super() 调用父类构造函数,super.method() 调用父类方法。
  • 子类重写的方法必须兼容父类签名,否则 TypeScript 报错。
  • override 关键字(TS 4.3+)显式标记重写,防止重构遗漏。
  • 多态让你用父类型引用操作子类对象,同一行代码对不同的子类产生不同行为。
  • instanceof 用于运行时的类型判断。

继承解决了”是什么”的关系。但在设计上,有时候你不希望基类被直接实例化——它存在的意义只是被继承。下一章讲抽象类。