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 #面向对象 #学习笔记 #前端开发

相关推荐
Molesidy10 分钟前
【GUI】【AWTK】基于开阳平台的GUI界面设计环境搭建
linux·gui·awtk
RisunJan21 分钟前
Linux命令-semanage(SELinux 策略管理)
linux·运维·服务器
流浪00129 分钟前
Linux系统篇 21:文件(五)——动静态库、ELF 底层原理全解
linux·运维·服务器
东方小月31 分钟前
从0开发一个 Coding Agent(一):前言
前端·人工智能·typescript
Dawn-bit36 分钟前
Linux救援模式和系统恢复
linux·运维·服务器·云计算·运维开发
十八岁牛爷爷41 分钟前
Linux 进程与进程状态・三层级深度解析
linux·运维·服务器
不会就选b1 小时前
linux之进程管理(二)--替换
linux·运维·服务器
倔强的石头1061 小时前
【Linux指南】动静态库系列(二):从源码复用到目标文件复用:为什么需要把 .o 打包成库
linux·运维·服务器
HLC++14 小时前
Linux的进程间通信
android·linux·服务器