TypeScript 类

TS 的类基于 ES6 的 class,但在它之上补齐了访问修饰符、参数属性、抽象类等面向对象的关键特性——这些都是纯 JS 没有的。这一章讲清楚 TS 类相比 JS 多出来的全部能力。如果你对 ES6 class 还不熟,建议先看 JS 系列的 ES6+ 一篇

1. 类的基本写法

和 JS class 最大的一点不同:字段必须先声明类型,不能像 JS 那样在构造函数里直接 this.x = x 凭空创建:

// TS 的类基于 ES6,增加了类型标注
class Animal {
  // 字段必须先声明类型(和 JS 不同)
  name: string;
  age: number;

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

  speak(): string {
    return `${this.name} 发出声音`;
  }
}

const a = new Animal("小白", 3);
console.log(a.speak());   // 小白 发出声音

2. 访问修饰符 public / private / protected

这是 TS 类相比 JS 最实用的增强。三个修饰符控制成员在哪里能被访问

// 访问修饰符:public(默认) / private / protected
class BankAccount {
  public owner: string;        // 任何地方都能访问
  private balance: number;     // 只能在本类内部访问
  protected id: string;        // 本类 + 子类内部访问

  constructor(owner: string, balance: number, id: string) {
    this.owner = owner;
    this.balance = balance;
    this.id = id;
  }

  public deposit(amount: number): void {
    this.balance += amount;    // ✅ 类内部访问 private
  }

  public getBalance(): number {
    return this.balance;
  }
}

const acc = new BankAccount("小明", 1000, "A001");
console.log(acc.owner);          // ✅ public 可访问
// console.log(acc.balance);     // ❌ private 不可访问
// console.log(acc.id);          // ❌ protected 不可访问

注意:private编译期检查,运行时并不真正阻止访问(编译成 JS 后修饰符被擦除)。如果你需要运行时也私密,可以用 ES2022 的 #字段 私有字段语法(如 #balance)。

3. 参数属性:TS 的语法糖

这是一个非常省事的特性。在构造函数参数前加修饰符,TS 会自动帮你声明同名字段并赋值,省掉一大堆样板代码:

// 参数属性:构造函数参数加修饰符,自动声明并赋值
// 这是 TS 独有的语法糖,省掉大量样板代码
class User {
  // 等价于:先声明字段 + 在构造函数里 this.x = x
  constructor(
    public name: string,        // 自动创建 public name 字段
    private age: number,        // 自动创建 private age 字段
    readonly id: number,        // 自动创建只读 id 字段
  ) {}
}

const u = new User("小明", 20, 1);
console.log(u.name);   // 小明
console.log(u.id);     // 1
// u.id = 2;           // ❌ readonly 不能改

对比第 1 节那种"先声明字段 + 构造函数里赋值"的写法,参数属性让代码量减半。这是 TS 类最常用的语法糖。

4. readonly 与抽象类

两个常用的特性:readonly 让属性只读;abstract 定义抽象类(只能被继承,不能直接实例化):

// readonly:只读属性,只能在声明时或构造函数里赋值
class Config {
  readonly version: string = "1.0.0";
  readonly createdAt: Date;

  constructor() {
    this.createdAt = new Date();   // 构造函数里可以赋值
  }
}

// 抽象类:只能被继承,不能直接实例化
abstract class Shape {
  abstract area(): number;        // 抽象方法:子类必须实现
  describe(): void {              // 普通方法:子类可以直接用
    console.log(`面积是 ${this.area()}`);
  }
}

class Circle extends Shape {
  constructor(public radius: number) { super(); }
  area(): number {                // 子类必须实现抽象方法
    return Math.PI * this.radius ** 2;
  }
}

// const s = new Shape();         // ❌ 抽象类不能实例化
const c = new Circle(2);
c.describe();                     // 面积是 12.566...

抽象类的价值在于定义统一接口 + 复用部分实现:抽象方法强制子类各自实现,普通方法让子类共享。这是面向对象设计的重要工具。

5. implements:实现接口

类可以用 implements 声明自己实现了哪些接口——接口只约束"形状",不提供实现:

// implements:类实现接口(只约束形状,不提供实现)
interface Loggable {
  log(msg: string): void;
}

class ConsoleLogger implements Loggable {
  log(msg: string): void {       // 必须实现 log 方法
    console.log(msg);
  }
}

// extends(继承)vs implements(实现):
//   extends    复用父类的实现(单继承)
//   implements 只遵守接口的形状(可多个)
class FileLogger extends ConsoleLogger implements Loggable {
  // 既有 ConsoleLogger 的实现,又满足 Loggable 接口
}

区分两个关键字:extends继承实现(子类拿到父类的代码),implements遵守契约(类必须自己实现接口规定的方法)。一个类只能 extends 一个父类,但可以 implements 多个接口。

6. 类也能用泛型

类同样可以带类型参数,这在泛型那一篇已经讲过,写法是 class Stack<T>。这里不再重复。

小结

TS 类 = ES6 class + 类型标注 + 访问修饰符 + 参数属性 + 抽象类 + 接口实现。最实用的是参数属性(省样板代码)和访问修饰符(封装)。下一篇讲 TS 的模块系统——import/export 怎么带类型。

← 上一篇 TypeScript 联合与交叉类型

下一篇 TypeScript 模块

✈️💬