TypeScript 类实现接口

本文献给:

已掌握 TypeScript 接口定义、可选属性、只读属性、继承等知识的开发者。本文将讲解类如何通过 implements 关键字实现一个或多个接口,以及类实现接口与抽象类的区别,为后续面向对象编程篇章打下基础。

你将学到:

  1. implements 关键字的基本用法
  2. 一个类实现多个接口
  3. 类实现接口时的类型检查规则
  4. 接口与抽象类的对比与选择
  5. 实现接口与继承类的组合使用

目录

  • [一、implements 关键字](#一、implements 关键字)
    • [1.1 类可以添加额外成员](#1.1 类可以添加额外成员)
  • 二、实现多个接口
    • [2.1 接口间的属性名冲突](#2.1 接口间的属性名冲突)
    • [2.2 方法名冲突](#2.2 方法名冲突)
  • 三、类实现接口时的类型检查
    • [3.1 属性必须初始化](#3.1 属性必须初始化)
    • [3.2 方法的参数和返回值必须匹配](#3.2 方法的参数和返回值必须匹配)
    • [3.3 可选属性的处理](#3.3 可选属性的处理)
  • 四、实现接口与继承类组合
    • [4.1 接口可以继承类](#4.1 接口可以继承类)
  • 五、接口与抽象类的对比
    • [5.1 何时用接口](#5.1 何时用接口)
    • [5.2 何时用抽象类](#5.2 何时用抽象类)
  • 六、常见错误与注意事项
    • [6.1 混淆 `extends` 和 `implements`](#6.1 混淆 extendsimplements)
    • [6.2 实现接口时忘记处理可选属性](#6.2 实现接口时忘记处理可选属性)
    • [6.3 接口定义的方法在类中实现时使用了错误的 `this` 类型](#6.3 接口定义的方法在类中实现时使用了错误的 this 类型)
    • [6.4 私有字段冲突](#6.4 私有字段冲突)
    • [6.5 实现多个接口时同名方法重载顺序](#6.5 实现多个接口时同名方法重载顺序)
  • 七、综合示例
  • 八、小结

一、implements 关键字

接口描述了类的公共契约(public contract)。类可以使用 implements 关键字声明它必须实现某个接口的所有成员。

typescript 复制代码
interface Drawable {
    draw(): void;
    color?: string;   // 可选属性
}

class Circle implements Drawable {
    draw() {
        console.log("Drawing circle");
    }
    // color 是可选的,可以不实现
}

const c: Drawable = new Circle();
c.draw();

如果类没有完整实现接口定义的成员,TypeScript 会报错。

typescript 复制代码
interface Point {
    x: number;
    y: number;
    distance(): number;
}

class MyPoint implements Point {
    x = 0;
    y = 0;
    // 缺少 distance 方法 ❌
}

1.1 类可以添加额外成员

实现接口的类可以拥有接口中未定义的额外属性和方法。

typescript 复制代码
interface Animal {
    name: string;
}

class Dog implements Animal {
    name = "Buddy";
    bark() {           // 额外方法
        console.log("woof");
    }
}

二、实现多个接口

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

typescript 复制代码
interface Printable {
    print(): void;
}

interface Serializable {
    toJSON(): string;
}

class Report implements Printable, Serializable {
    print() {
        console.log("Printing report");
    }
    toJSON() {
        return JSON.stringify({ content: "report" });
    }
}

2.1 接口间的属性名冲突

如果两个接口有同名但类型不同的属性,类必须实现一个同时满足两种类型的成员。这通常意味着需要联合类型或无法满足。

typescript 复制代码
interface A {
    value: string;
}
interface B {
    value: number;
}
class C implements A, B {
    value: string | number = "hello";  // 联合类型可同时满足
}

如果类型完全不兼容(如 stringnumber 没有交集),则无法实现。

2.2 方法名冲突

如果两个接口有同名方法但签名不同,类中需要实现为重载。

typescript 复制代码
interface Clickable {
    click(): void;
}
interface Draggable {
    click(x: number, y: number): void;
}
class UIElement implements Clickable, Draggable {
    click(x?: number, y?: number): void {
        if (x !== undefined && y !== undefined) {
            console.log(`Dragged to ${x},${y}`);
        } else {
            console.log("Clicked");
        }
    }
}

三、类实现接口时的类型检查

3.1 属性必须初始化

接口中声明的属性,在类中必须被初始化(构造函数中赋值或声明时赋默认值),或标记为可选。

typescript 复制代码
interface Config {
    url: string;
    timeout: number;
}

class HttpClient implements Config {
    url: string;
    timeout: number;
    constructor(url: string) {
        this.url = url;
        this.timeout = 5000;  // 初始化
    }
}

3.2 方法的参数和返回值必须匹配

typescript 复制代码
interface Formatter {
    format(input: string): string;
}

class UpperFormatter implements Formatter {
    format(input: number): number {  // ❌ 参数类型不匹配
        return input;
    }
}

3.3 可选属性的处理

接口中的可选属性,类可以不实现。

typescript 复制代码
interface Logger {
    log(msg: string): void;
    level?: string;
}

class ConsoleLogger implements Logger {
    log(msg: string) {
        console.log(msg);
    }
    // level 未实现,允许
}

四、实现接口与继承类组合

类可以同时继承一个父类并实现一个或多个接口。继承在前,implements 在后。

typescript 复制代码
class BaseEntity {
    id: number = 0;
}

interface Timestamped {
    createdAt: Date;
    updatedAt: Date;
}

class User extends BaseEntity implements Timestamped {
    name: string;
    createdAt: Date;
    updatedAt: Date;
    constructor(name: string) {
        super();
        this.name = name;
        this.createdAt = new Date();
        this.updatedAt = new Date();
    }
}

4.1 接口可以继承类

如之前所学,接口可以继承类,然后由另一个类实现。但这要求实现类必须是该类的子类。

typescript 复制代码
class Control {
    private state: boolean;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() {}
}
// class Fake implements SelectableControl {} // ❌ 不是 Control 子类

五、接口与抽象类的对比

接口和抽象类都用于定义契约,但差异明显。

特性 接口 抽象类
实例化 不能 不能
实现方法体 不能(TS 接口只有声明) 可以有具体方法
构造器 可以有
访问修饰符 所有成员隐含 public 支持 privateprotected
多继承 一个类可实现多个接口 一个类只能继承一个抽象类
存储状态(字段) 只能声明,不能初始化 可以声明并初始化字段
运行时存在 不存在(编译后消失) 存在(编译为 JS 类)

5.1 何时用接口

  • 描述对象的结构(类似于"鸭子类型")
  • 需要让一个类实现多个契约
  • 与第三方库或 API 定义类型(轻量、无运行时开销)
  • 类的实现完全不相关,只关心公共方法签名

5.2 何时用抽象类

  • 需要提供部分默认实现
  • 需要共享字段(状态)给子类
  • 需要 protected 成员或构造函数逻辑
  • 需要运行时存在(如 instanceof 检查)

六、常见错误与注意事项

6.1 混淆 extendsimplements

类继承使用 extends,实现接口使用 implements。不能混用。

typescript 复制代码
class A implements B  // B 是接口
class C extends D     // D 是类

6.2 实现接口时忘记处理可选属性

接口有可选属性,类可以不实现,但如果类中实现了该属性,类型必须匹配。

6.3 接口定义的方法在类中实现时使用了错误的 this 类型

方法签名中的 this 参数需要匹配。通常不需要显式写。

6.4 私有字段冲突

接口不能包含私有字段,因此无法强制类实现私有成员。这是接口与抽象类的关键区别。

6.5 实现多个接口时同名方法重载顺序

重载签名需要正确排列,实现签名必须兼容所有重载。

七、综合示例

typescript 复制代码
// 定义两个接口
interface Eater {
    eat(food: string): void;
}

interface Sleeper {
    sleep(hours: number): void;
}

interface Reproducible {
    reproduce(): void;
}

// 抽象类实现部分默认行为
abstract class Animal implements Eater, Sleeper {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    
    eat(food: string) {
        console.log(`${this.name} eats ${food}`);
    }
    
    sleep(hours: number) {
        console.log(`${this.name} sleeps for ${hours} hours`);
    }
    
    abstract makeSound(): void;  // 抽象方法
}

// 具体类
class Dog extends Animal implements Reproducible {
    makeSound() {
        console.log("Woof!");
    }
    
    reproduce() {
        console.log(`${this.name} gives birth to puppies`);
    }
    
    // 额外方法
    fetch() {
        console.log(`${this.name} fetches the ball`);
    }
}

class Cat extends Animal {
    makeSound() {
        console.log("Meow!");
    }
    
    // Cat 不实现 Reproducible
}

// 使用
const dog = new Dog("Buddy");
dog.eat("bone");
dog.sleep(8);
dog.makeSound();
dog.reproduce();
dog.fetch();

const cat = new Cat("Whiskers");
cat.eat("fish");

// 接口作为类型约束
function takeNap(animal: Sleeper) {
    animal.sleep(1);
}
takeNap(dog);
takeNap(cat);

// 多个接口组合的场景:可序列化的用户
interface Serializable {
    serialize(): string;
}

interface Identifiable {
    id: number;
}

class User implements Identifiable, Serializable {
    id: number;
    name: string;
    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }
    serialize(): string {
        return JSON.stringify({ id: this.id, name: this.name });
    }
}

const u = new User(1, "Alice");
console.log(u.serialize());

八、小结

概念 语法示例 说明
实现单接口 class A implements B 必须实现 B 的所有成员
实现多接口 class A implements B, C 必须实现 B 和 C 的所有成员
继承+实现 class A extends B implements C 先继承再实现
可选接口属性 类可以不实现,但如果实现必须类型匹配 提升灵活性
接口 vs 抽象类 接口纯契约(无实现),抽象类可含实现和状态 按需选择

觉得文章有帮助?别忘了:

👍 点赞 👍 -- 给我一点鼓励
⭐ 收藏 ⭐ -- 方便以后查看
🔔 关注 🔔 -- 获取更新通知


标签: #TypeScript #类实现接口 #implements #面向对象 #学习笔记 #前端开发

相关推荐
妙码生花8 小时前
现代前端的极致性能 icon 加载方案(死磕成功版)
前端·vue.js·typescript
MonkeyKing12 小时前
鸿蒙ArkTS深度剖析:ArkTS与TS/JS核心差异、静态强类型实战优势
typescript·harmonyos
AlfredZhao1 天前
生产环境里,为什么不建议把普通端口直接暴露到公网?
linux·https·443·80
Momo__2 天前
TypeScript satisfies 操作符——比 as 更安全的类型守门员
前端·typescript
戴为沐3 天前
Linux内存扩容指南
linux
zylyehuo3 天前
Linux 彻底且安全地删除文件
linux
Awu12273 天前
⚡从零开发 Agent CLI(四):给 CLI 装上"LLM 引擎"
typescript·ai编程·claude
用户805533698033 天前
主线 U-Boot 上 RK3506:和闭源 rkbin 拔河的三个隐性契约
linux·嵌入式
用户034095297913 天前
linux fcitx 5 雾凇拼音 设置在中文输入法下仍然输入英文标点
linux
假如让我当三天老蒯4 天前
TypeScript 继续学习(学习用)
前端·面试·typescript