继承与多态
本教程共 80 篇 · 第 33 篇 · 更新于 2026-08-10 · 约 11 分钟阅读
本节目标:学会用 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用于运行时的类型判断。
继承解决了”是什么”的关系。但在设计上,有时候你不希望基类被直接实例化——它存在的意义只是被继承。下一章讲抽象类。