首页 / TypeScript 入门教程 / 类实现接口

TypeScript 入门教程

类实现接口

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

TypeScriptTypeScript 入门教程接口implements

本节目标:掌握 implements 关键字的用法,理解”接口定义契约、类提供实现”的协作模式,能区分 extends 和 implements 的使用场景。

第 24 章学的接口可以定义对象形状,第 33 章学的继承可以让子类复用父类代码。在面向对象设计里,还有一个重要的关系:“这个类承诺提供某些能力”。这就是接口和类之间的 implements 关系。

implements:类兑现接口

interface Flyable {
  fly(): void;
  land(): void;
}

class Bird implements Flyable {
  fly(): void {
    console.log("鸟儿在飞");
  }

  land(): void {
    console.log("鸟儿降落");
  }
}

class Airplane implements Flyable {
  fly(): void {
    console.log("飞机起飞");
  }

  land(): void {
    console.log("飞机着陆");
  }
}

Bird implements Flyable 的意思是:“我承诺 Bird 类具备 Flyable 接口要求的所有属性和方法。“如果少了任何一个,TypeScript 马上报错:

class BrokenBird implements Flyable {
  // ❌ Class 'BrokenBird' incorrectly implements interface 'Flyable'.
  //    Property 'fly' is missing in type 'BrokenBird'.
  land(): void {
    console.log("尝试降落");
  }
}

接口就是一份合同。implements 是你在合同上签字——签了就得履行,缺一条都不行。

实现多个接口

一个类可以实现多个接口,用逗号分隔:

interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

interface Runnable {
  run(): void;
}

class Duck implements Flyable, Swimmable, Runnable {
  fly(): void {
    console.log("鸭子飞起来了");
  }

  swim(): void {
    console.log("鸭子在游泳");
  }

  run(): void {
    console.log("鸭子在跑");
  }
}

这弥补了 JavaScript / TypeScript 只支持单继承的局限——虽然你只能 extends 一个类,但可以 implements 任意多个接口。

接口定义契约,类提供实现

implements 的一个关键价值是解耦。消费代码只依赖接口,不依赖具体类:

interface Logger {
  log(message: string): void;
  error(message: string): void;
}

class ConsoleLogger implements Logger {
  log(message: string): void {
    console.log(`[INFO] ${message}`);
  }

  error(message: string): void {
    console.error(`[ERROR] ${message}`);
  }
}

class FileLogger implements Logger {
  private filePath: string;

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

  log(message: string): void {
    // 实际项目里会写文件,这里用输出模拟
    console.log(`[FILE:${this.filePath}] [INFO] ${message}`);
  }

  error(message: string): void {
    console.log(`[FILE:${this.filePath}] [ERROR] ${message}`);
  }
}

// 函数只依赖 Logger 接口,不关心具体实现
function doWork(logger: Logger): void {
  logger.log("开始执行任务");
  // ... 执行任务 ...
  logger.log("任务完成");
}

doWork(new ConsoleLogger());
doWork(new FileLogger("app.log"));

将来你想加一个 CloudLogger,只要它 implements LoggerdoWork() 函数一行都不用改。这就是面向接口编程的好处。

implements 不会改变类的类型

这是一个经典的误解:以为 implements 会影响类的类型推断。它不会。

interface Named {
  name: string;
}

class Person implements Named {
  name: string;
  age: number;

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

const p = new Person("Alice", 30);
// p 的类型是 Person,不是 Named
console.log(p.age); // ✅ 可以访问 age,因为类型是 Person

implements 只是检查”类是否满足接口”,不会改变类本身的类型。这和第 24 章接口的 extends 不一样——interface A extends B 会合并类型。

另一个容易踩的坑:implements 不会影响类内部方法的参数类型推断

interface Checkable {
  check(name: string): boolean;
}

class NameChecker implements Checkable {
  check(s) {    // s 的类型是 any,不是 string!
    // 有人的预期是 s 的类型会从接口中推断为 string,但不会
    return s.toLowerCase() === "ok";
  }
}

类内部方法的参数类型,还是得自己在类里写。接口不会”渗透”进去。

implements 可选属性不会自动创建

接口里的可选属性,implements 之后不会自动出现在类上:

interface Config {
  url: string;
  timeout?: number;
}

class AppConfig implements Config {
  url = "https://api.example.com";
  // timeout 是可选属性,可以不写
}

const config = new AppConfig();
// config.timeout = 5000;  // ❌ AppConfig 类型上没有 timeout 属性

如果你想在实例上访问 timeout,得在类里显式声明。但在结构类型系统里,用接口类型来接收就没问题:

const config: Config = new AppConfig();
config.timeout = 5000;  // ✅ 通过 Config 类型访问,没问题

implements vs extends

维度extends(类继承)implements(接口实现)
关键词”是一个""能做”
继承/实现数量只能一个可以多个
共享代码✅ 继承父类的属性和方法❌ 只约束类型,不给代码
父/接口能否实例化可以(除非抽象类)不能
编译后保留继承关系完全抹除
典型场景子类是父类的特化版本不相关的类共享同一套能力

一句话总结:extends 拿代码,implements 拿契约。

用现实世界类比:Dog extends Animal 是说”狗是一种动物”(is-a)。Dog implements Runnable 是说”狗具备跑的能力”(can-do)。

同时使用 extends 和 implements

一个类可以同时继承一个父类并实现多个接口:

interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

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

  breathe(): void {
    console.log(`${this.name} 在呼吸`);
  }
}

class Duck extends Animal implements Flyable, Swimmable {
  fly(): void {
    console.log(`${this.name} 飞起来`);
  }

  swim(): void {
    console.log(`${this.name} 在游泳`);
  }
}

const duck = new Duck("唐老鸭");
duck.breathe();   // 从 Animal 继承
duck.fly();       // 实现 Flyable
duck.swim();      // 实现 Swimmable

这里的 Duck 既继承了 Animal 的全部代码(namebreathe()),又兑现了 FlyableSwimmable 的契约。

Tip

在设计类的时候,建议先想清楚”这个类是什么”(继承关系)和”这个类能做什么”(实现的接口)。把”是什么”用 extends 串起来,把”能做什么”用 implements 列出来。这样设计出来的类层次更清晰。

接口继承接口 + 类实现接口

接口之间也可以用 extends 继承,这是和第 25 章讲的一样。然后类再 implements 最终的接口:

interface Readable {
  read(): string;
}

interface Writable {
  write(data: string): void;
}

interface ReadWrite extends Readable, Writable {
  // 继承了 read() 和 write()
}

class FileHandler implements ReadWrite {
  private content = "";

  read(): string {
    return this.content;
  }

  write(data: string): void {
    this.content += data;
  }
}

这种模式在大型项目里很常见——顶层接口定义能力原子,中间接口组合能力,类最后实现。

小结

  • implements 让类兑现接口契约,缺少成员会报错。
  • 一个类可以实现多个接口,弥补了单继承的限制。
  • implements 只做类型检查,不影响类的类型推断和内部类型。
  • 接口的可选属性不会自动添加到类上。
  • extends 拿代码(is-a),implements 拿契约(can-do)。
  • 一个类可以同时 extends 一个父类并 implements 多个接口。

到目前为止,我们学的都是单继承体系。但现实中有时候一个类需要从多个来源组合行为——日志、序列化、缓存、时间戳等等。单继承做不到,接口又只能定义签名不能给代码。有没有办法两全其美?有——这就是下一章的 Mixin 混入模式。