前置知识: JavaScript

类与装饰器

11 min中级

类定义、访问修饰符、装饰器模式与元数据。

前置知识

学习目标

  • 掌握「1. 类成员修饰符 (Access Modifiers)」的核心机制、典型用法与常见陷阱
  • 掌握「2. 构造函数简写」的核心机制、典型用法与常见陷阱
  • 掌握「3. 抽象类 (Abstract Classes)」的核心机制、典型用法与常见陷阱
  • 掌握「4. 静态成员」的核心机制、典型用法与常见陷阱
  • 掌握「5. 类的存取器 (Getters & Setters)」的核心机制、典型用法与常见陷阱

阅读提示:正文以代码和白话为主,不出现类型论公式。进阶文档中若出现 Γ ⊢ e : τ 这类记号,第一遍可完全跳过(完整规则见 001-HowToReadThisCourse)。

1. 类成员修饰符 (Access Modifiers)

TypeScript 提供了四种访问修饰符,用于控制类成员的访问权限:

1.1 访问修饰符详解

修饰符说明可访问范围
public默认修饰符,公共成员任何位置都可以访问
private私有成员仅在类内部可以访问
protected受保护成员类内部和子类中可以访问
readonly只读成员仅在构造函数中初始化,之后不可修改

1.2 访问修饰符使用示例

class Person {
  // 公共成员
  public name: string;
  // 私有成员
  private age: number;
  // 受保护成员
  protected gender: string;
  // 只读成员
  readonly id: number;
  constructor(name: string, age: number, gender: string, id: number) {
    this.name = name;
    this.age = age;
    this.gender = gender;
    this.id = id;
  }
  // 类内部可以访问所有成员
  public getInfo(): string {
    return `Name: ${this.name}, Age: ${this.age}, Gender: ${this.gender}, ID: ${this.id}`;
  }
  // 私有方法
  private calculateBirthYear(): number {
    const currentYear = new Date().getFullYear();
    return currentYear - this.age;
  }
  // 公共方法访问私有方法
  public getBirthYear(): number {
    return this.calculateBirthYear();
  }
}
// 使用示例
const person = new Person('Alice', 30, 'female', 12345);
console.log(person.name); // 可以访问,输出: Alice
// console.log(person.age); // 编译错误,私有成员不能在类外部访问
// console.log(person.gender); // 编译错误,受保护成员不能在类外部访问
console.log(person.id); // 可以访问,输出: 12345
// person.id = 67890; // 编译错误,只读成员不能修改
console.log(person.getInfo()); // 可以访问,输出: Name: Alice, Age: 30, Gender: female, ID: 12345
console.log(person.getBirthYear()); // 可以访问,输出: 1994(假设当前年份为2024)
// 子类继承
class Employee extends Person {
  constructor(
    name: string,
    age: number,
    gender: string,
    id: number,
    public position: string
  ) {
    super(name, age, gender, id);
  }
  // 子类可以访问受保护成员
  public getEmployeeInfo(): string {
    return `${this.getInfo()}, Position: ${this.position}, Gender: ${this.gender}`;
  }
  // 子类不能访问私有成员
  // public getAge(): number {
  // return this.age; // 编译错误
  // }
}
const employee = new Employee('Bob', 25, 'male', 67890, 'Developer');
console.log(employee.name); // 可以访问
console.log(employee.position); // 可以访问
console.log(employee.getEmployeeInfo()); // 可以访问,输出包含 gender
// console.log(employee.gender); // 编译错误,受保护成员不能在类外部访问

1.3 访问修饰符的最佳实践

  • 最小权限原则: 尽量使用最严格的访问修饰符,只暴露必要的成员。
  • 封装性: 使用 private 修饰符隐藏内部实现细节。
  • 继承设计: 使用 protected 修饰符允许子类访问必要的成员。
  • 不可变性: 使用 readonly 修饰符确保成员在初始化后不被修改。
  • 代码可读性: 明确指定访问修饰符,提高代码可读性。

2. 构造函数简写

TypeScript 提供了构造函数简写语法,可以在构造函数参数中直接声明类成员,简化代码。

2.1 基本用法

// 传统写法
class User {
  public name: string;
  private age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
// 构造函数简写
class User {
  constructor(
    public name: string,
    private age: number
  ) {}
}
// 使用示例
const user = new User('Alice', 30);
console.log(user.name); // 输出: Alice
// console.log(user.age); // 编译错误,私有成员

2.2 构造函数简写与访问修饰符

class Product {
  constructor(
    public id: number,
    public name: string,
    private price: number,
    protected stock: number,
    readonly category: string
  ) {}
  public getPrice(): number {
    return this.price;
  }
  public getStock(): number {
    return this.stock;
  }
}
// 使用示例
const product = new Product(1, 'Laptop', 999.99, 50, 'Electronics');
console.log(product.id); // 输出: 1
console.log(product.name); // 输出: Laptop
console.log(product.category); // 输出: Electronics
// product.category = "Computers"; // 编译错误,只读
console.log(product.getPrice()); // 输出: 999.99
console.log(product.getStock()); // 输出: 50

2.3 构造函数简写与默认值

 class Person {
  constructor(
  public name: string,
  public age: number = 18,
  private isActive: boolean =
  ) {}
  public getStatus(): string {
  return this.isActive ? "Active" : "Inactive";
  }
 }
 // 使用示例
 const person1 = new Person("Alice", 30);
 console.log(person1.name); // 输出: Alice
 console.log(person1.age); // 输出: 30
 console.log(person1.getStatus()); // 输出: Active
 const person2 = new Person("Bob");
 console.log(person2.name); // 输出: Bob
 console.log(person2.age); // 输出: 18(使用默认值)
 console.log(person2.getStatus()); // 输出: Active(使用默认值)

3. 抽象类 (Abstract Classes)

抽象类是一种不能直接实例化的类,主要用于作为其他类的基类,定义共同的接口和行为。

3.1 基本概念

  • 抽象类: 使用 abstract 关键字声明,不能直接实例化。
  • 抽象方法: 使用 abstract 关键字声明,没有具体实现,必须在子类中实现。
  • 具体方法: 抽象类中可以包含具体实现的方法。

3.2 抽象类使用示例

// 抽象基类
abstract class Shape {
  // 抽象方法
  abstract getArea(): number;
  // 抽象方法
  abstract getPerimeter(): number;
  // 具体方法
  public printInfo(): void {
    console.log(`Area: ${this.getArea()}, Perimeter: ${this.getPerimeter()}`);
  }
}
// 实现抽象类
class Circle extends Shape {
  constructor(private radius: number) {
    super();
  }
  // 实现抽象方法
  getArea(): number {
    return Math.PI * this.radius * this.radius;
  }
  // 实现抽象方法
  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}
class Rectangle extends Shape {
  constructor(
    private width: number,
    private height: number
  ) {
    super();
  }
  // 实现抽象方法
  getArea(): number {
    return this.width * this.height;
  }
  // 实现抽象方法
  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }
}
// 使用示例
const circle = new Circle(5);
console.log(circle.getArea()); // 输出: 78.53981633974483
console.log(circle.getPerimeter()); // 输出: 31.41592653589793
circle.printInfo(); // 输出: Area: 78.53981633974483, Perimeter: 31.41592653589793
const rectangle = new Rectangle(4, 6);
console.log(rectangle.getArea()); // 输出: 24
console.log(rectangle.getPerimeter()); // 输出: 20
rectangle.printInfo(); // 输出: Area: 24, Perimeter: 20
// 错误示例:抽象类不能直接实例化
// const shape = new Shape(); // 编译错误

3.3 抽象类与接口的区别

特性抽象类接口
实现可以包含具体实现只能定义方法签名,不能包含实现
访问修饰符可以使用访问修饰符所有成员默认为 public
构造函数可以有构造函数不能有构造函数
继承只能继承一个抽象类可以实现多个接口
字段可以包含实例字段不能包含实例字段(TypeScript 2.7+ 可以定义 readonly 字段)

3.4 抽象类的最佳实践

  • 定义共同行为: 使用抽象类定义一组相关类的共同行为和接口。
  • 强制实现: 通过抽象方法强制子类实现特定功能。
  • 代码复用: 在抽象类中实现共同的逻辑,子类可以直接使用。
  • 层次结构: 使用抽象类创建清晰的类层次结构。

4. 静态成员

静态成员属于类本身,而不是类的实例,可以通过类名直接访问。

4.1 静态属性和方法

class MathUtils {
  // 静态属性
  static PI: number = 3.14159;
  // 静态方法
  static add(a: number, b: number): number {
    return a + b;
  }
  static multiply(a: number, b: number): number {
    return a * b;
  }
  // 静态方法调用静态属性
  static calculateCircleArea(radius: number): number {
    return this.PI * radius * radius;
  }
}
// 使用示例
console.log(MathUtils.PI); // 输出: 3.14159
console.log(MathUtils.add(5, 3)); // 输出: 8
console.log(MathUtils.multiply(4, 6)); // 输出: 24
console.log(MathUtils.calculateCircleArea(5)); // 输出: 78.53975
// 错误示例:静态成员不能通过实例访问
// const math = new MathUtils();
// console.log(math.PI); // 编译错误

4.2 静态成员与实例成员

class Counter {
  // 静态属性
  static count: number = 0;
  // 实例属性
  private id: number;
  constructor() {
    // 访问静态属性
    Counter.count++;
    this.id = Counter.count;
  }
  // 实例方法
  public getId(): number {
    return this.id;
  }
  // 静态方法
  static getTotalCount(): number {
    return Counter.count;
  }
}
// 使用示例
const counter1 = new Counter();
console.log(counter1.getId()); // 输出: 1
console.log(Counter.getTotalCount()); // 输出: 1
const counter2 = new Counter();
console.log(counter2.getId()); // 输出: 2
console.log(Counter.getTotalCount()); // 输出: 2
const counter3 = new Counter();
console.log(counter3.getId()); // 输出: 3
console.log(Counter.getTotalCount()); // 输出: 3

4.3 静态成员的最佳实践

  • 工具方法: 使用静态方法实现不依赖实例状态的工具函数。
  • 常量定义: 使用静态属性定义类级别的常量。
  • 共享状态: 使用静态属性在类的所有实例之间共享状态。
  • 命名空间: 使用静态成员创建命名空间,组织相关功能。

5. 类的存取器 (Getters & Setters)

存取器允许我们控制对类属性的访问和修改,提供了一种封装属性的方式。

5.1 基本用法

class Person {
  private _name: string;
  private _age: number;
  constructor(name: string, age: number) {
    this._name = name;
    this._age = age;
  }
  // getter
  get name(): string {
    return this._name;
  }
  // setter
  set name(value: string) {
    if (value.length > 0) {
      this._name = value;
    } else {
      throw new Error('Name cannot be empty');
    }
  }
  // getter
  get age(): number {
    return this._age;
  }
  // setter
  set age(value: number) {
    if (value >= 0 && value <= 120) {
      this._age = value;
    } else {
      throw new Error('Age must be between 0 and 120');
    }
  }
}
// 使用示例
const person = new Person('Alice', 30);
console.log(person.name); // 输出: Alice
console.log(person.age); // 输出: 30
// 使用 setter 修改属性
person.name = 'Bob';
person.age = 25;
console.log(person.name); // 输出: Bob
console.log(person.age); // 输出: 25
// 错误示例:无效的输入
// person.name = ""; // 抛出错误: Name cannot be empty
// person.age = 150; // 抛出错误: Age must be between 0 and 120

5.2 存取器与访问修饰符

class Product {
  private _price: number;
  constructor(
    private _id: number,
    private _name: string,
    price: number
  ) {
    this._price = price;
  }
  // 只读属性(只有 getter)
  get id(): number {
    return this._id;
  }
  // 只读属性(只有 getter)
  get name(): string {
    return this._name;
  }
  // 可读写属性(有 getter 和 setter)
  get price(): number {
    return this._price;
  }
  set price(value: number) {
    if (value > 0) {
      this._price = value;
    } else {
      throw new Error('Price must be positive');
    }
  }
}
// 使用示例
const product = new Product(1, 'Laptop', 999.99);
console.log(product.id); // 输出: 1
console.log(product.name); // 输出: Laptop
console.log(product.price); // 输出: 999.99
// 修改价格
product.price = 899.99;
console.log(product.price); // 输出: 899.99
// 错误示例:尝试修改只读属性
// product.id = 2; // 编译错误
// product.name = "Desktop"; // 编译错误

5.3 存取器的最佳实践

  • 数据验证: 在 setter 中添加数据验证逻辑,确保属性值的有效性。
  • 封装性: 使用存取器封装内部状态,只暴露必要的接口。
  • 只读属性: 对于不需要修改的属性,只提供 getter。
  • 计算属性: 使用 getter 实现计算属性,根据其他属性动态计算值。

6. 装饰器 (Decorators)

装饰器是 TypeScript 的一个实验性特性,允许我们修改类、方法、属性和参数的行为。

6.1 装饰器的基本概念

  • 装饰器是一个函数,用于修改类、方法、属性或参数的行为。
  • 装饰器语法使用 @ 符号,后面跟着装饰器函数名。
  • 装饰器执行时机:在类定义时执行,而不是在实例化时执行。
  • 两套实现,别混淆:TypeScript 5.0(2023-03)起默认支持 TC39 标准装饰器(提案截至 2026-09 仍为 Stage 3 未定稿),签名为 (value, context) 两参数;本节示例采用旧的实验性装饰器(需在 tsconfig.json 开启 experimentalDecorators,(target, key, descriptor) 三参数风格),它仍被 Angular、NestJS 等存量生态广泛使用。新项目请优先使用标准装饰器,完整展开见 typescript/280-DecoratorStandardImpl 与 typescript/270-DecoratorDetailed。

6.2 装饰器的类型

实验性装饰器支持五种装饰器;标准装饰器(TS 5.0+)支持前四种(不含参数装饰器):

  1. 类装饰器:应用于类声明
  2. 方法装饰器:应用于类方法
  3. 属性装饰器:应用于类属性
  4. 访问器装饰器:应用于 getter/setter
  5. 参数装饰器:应用于方法参数(仅实验性装饰器支持)

6.2.1 标准装饰器签名对照

同样是”方法日志”,标准装饰器这样写(无需任何编译选项):

// 标准装饰器(TS 5.0+ 默认启用):签名是 (value, context) 两个参数
function logStandard<T extends (...args: any[]) => any>(
  value: T,
  context: ClassMethodDecoratorContext<unknown, T>,
): T {
  return function (this: unknown, ...args: any[]) {
    console.log(`[${String(context.name)}] called with:`, args);
    return value.apply(this, args);
  } as T;
}
class Greeting {
  @logStandard
  hello(name: string) { return `Hello, ${name}`; }
}
console.log(new Greeting().hello('TS'));
// 输出: [hello] called with: ['TS']
//       Hello, TS

6.3 类装饰器

类装饰器接收一个参数:目标类的构造函数。(本节起示例均为实验性装饰器签名,需 tsconfig 开启 experimentalDecorators。)

// 类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
  console.log(`Class ${constructor.name} has been sealed`);
}
// 带参数的类装饰器
function logClass(prefix: string) {
  return function (constructor: Function) {
    console.log(`${prefix}: ${constructor.name} class defined`);
  };
}
// 使用类装饰器
@logClass('INFO')
@sealed
class Person {
  constructor(
    public name: string,
    public age: number
  ) {}
  public greet(): string {
    return `Hello, my name is ${this.name}`;
  }
}
// 使用示例
const person = new Person('Alice', 30);
console.log(person.greet()); // 输出: Hello, my name is Alice
// 尝试修改类(由于 sealed 装饰器,会失败)
// Person.prototype.newMethod = function() {}; // 在严格模式下会抛出错误

6.4 方法装饰器

方法装饰器接收三个参数:

  1. 目标对象(对于静态方法是类构造函数,对于实例方法是类原型)
  2. 方法名
  3. 方法的属性描述符
// 方法装饰器
function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Method ${key} called with args: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${key} returned: ${JSON.stringify(result)}`);
    return result;
  };
  return descriptor;
}
// 带参数的方法装饰器
function measureTime(unit: 'ms' | 's' = 'ms') {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const start = performance.now();
      const result = originalMethod.apply(this, args);
      const end = performance.now();
      const duration = end - start;
      const formattedDuration = unit === 's' ? duration / 1000 : duration;
      console.log(`Method ${key} took ${formattedDuration} ${unit}`);
      return result;
    };
    return descriptor;
  };
}
class Calculator {
  @logMethod
  add(a: number, b: number): number {
    return a + b;
  }
  @measureTime('s')
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}
// 使用示例
const calculator = new Calculator();
console.log(calculator.add(5, 3)); // 输出: 8
console.log(calculator.fibonacci(30)); // 输出斐波那契数并显示执行时间

6.5 属性装饰器

属性装饰器接收两个参数:

  1. 目标对象(对于静态属性是类构造函数,对于实例属性是类原型)
  2. 属性名
 // 属性装饰器
 function logProperty(target: any, key: string) {
  let value = target[key];
  // 定义 getter
  const getter = function() {
  console.log(`Getting ${key}: ${value}`);
  return value;
  };
  // 定义 setter
  const setter = function(newValue: any) {
  console.log(`Setting ${key} from ${value} to ${newValue}`);
  value = newValue;
  };
  // 重新定义属性
  Object.defineProperty(target, key, {
  get: getter,
  set: setter,
  enumerable: true,
  configurable:
  });
 }
 class Person {
  @logProperty
  public name: string;
  @logProperty
  public age: number;
  constructor(name: string, age: number) {
  this.name = name;
  this.age = age;
  }
 }
 // 使用示例
 const person = new Person("Alice", 30);
 console.log(person.name); // 输出: Getting name: Alice
 person.name = "Bob"; // 输出: Setting name from Alice to Bob
 console.log(person.age); // 输出: Getting age: 30
 person.age = 25; // 输出: Setting age from 30 to 25

6.6 参数装饰器

参数装饰器接收三个参数:

  1. 目标对象(对于静态方法是类构造函数,对于实例方法是类原型)
  2. 方法名
  3. 参数在方法参数列表中的索引
// 参数装饰器
function logParameter(target: any, key: string, index: number) {
  console.log(`Parameter decorator applied to ${key} at index ${index}`);
}
class UserService {
  getUser(@logParameter id: number, @logParameter name: string): string {
    return `User: ${name} (ID: ${id})`;
  }
}
// 使用示例
const userService = new UserService();
console.log(userService.getUser(1, 'Alice')); // 输出: User: Alice (ID: 1)

6.7 装饰器的执行顺序

多个装饰器应用于同一个声明时,执行顺序如下:

  1. 参数装饰器:从左到右执行
  2. 方法装饰器:从右到左执行
  3. 属性装饰器:从右到左执行
  4. 类装饰器:从右到左执行
function decorator1() {
  console.log('Decorator 1 applied');
  return function (target: any, key?: string, descriptor?: PropertyDescriptor) {
    console.log('Decorator 1 executed');
  };
}
function decorator2() {
  console.log('Decorator 2 applied');
  return function (target: any, key?: string, descriptor?: PropertyDescriptor) {
    console.log('Decorator 2 executed');
  };
}
@decorator1()
@decorator2()
class Example {
  @decorator1()
  @decorator2()
  public property: string;
  @decorator1()
  @decorator2()
  public method(@decorator1() @decorator2() param: string): void {}
  constructor() {
    this.property = 'test';
  }
}
// 执行顺序:
// Decorator 2 applied
// Decorator 1 applied
// Decorator 2 applied
// Decorator 1 applied
// Decorator 2 applied
// Decorator 1 applied
// Parameter decorator applied to method at index 0 (decorator2)
// Parameter decorator applied to method at index 0 (decorator1)
// Decorator 2 executed (method)
// Decorator 1 executed (method)
// Decorator 2 executed (property)
// Decorator 1 executed (property)
// Decorator 2 executed (class)
// Decorator 1 executed (class)

6.8 装饰器的应用场景

装饰器在以下场景中特别有用:

  1. 日志记录:记录方法调用、参数和返回值
  2. 性能监控:测量方法执行时间
  3. 权限控制:检查用户权限
  4. 数据验证:验证方法参数和属性值
  5. 依赖注入:自动注入依赖项
  6. 缓存:缓存方法结果
  7. 错误处理:统一处理方法执行过程中的错误

6.9 装饰器的最佳实践

  • 明确目的:装饰器应该有明确的职责,不要过度使用。
  • 保持简洁:装饰器逻辑应该简洁明了,避免复杂的实现。
  • 可组合性:设计装饰器时考虑可组合性,允许多个装饰器一起使用。
  • 性能考虑:装饰器在类定义时执行,避免在装饰器中执行耗时操作。
  • 文档化:为装饰器添加清晰的文档,说明其用途和使用方法。

7. 类的继承与多态

TypeScript 支持类的继承,允许子类继承父类的属性和方法。

7.1 基本继承

class Animal {
  constructor(public name: string) {}
  public makeSound(): void {
    console.log(`${this.name} makes a sound`);
  }
  public move(): void {
    console.log(`${this.name} moves`);
  }
}
class Dog extends Animal {
  constructor(
    name: string,
    public breed: string
  ) {
    super(name); // 调用父类构造函数
  }
  // 重写父类方法
  public makeSound(): void {
    console.log(`${this.name} barks`);
  }
  // 新增方法
  public fetch(): void {
    console.log(`${this.name} fetches a ball`);
  }
}
class Cat extends Animal {
  constructor(
    name: string,
    public color: string
  ) {
    super(name);
  }
  // 重写父类方法
  public makeSound(): void {
    console.log(`${this.name} meows`);
  }
  // 新增方法
  public climb(): void {
    console.log(`${this.name} climbs a tree`);
  }
}
// 使用示例
const dog = new Dog('Buddy', 'Golden Retriever');
dog.makeSound(); // 输出: Buddy barks
dog.move(); // 输出: Buddy moves
dog.fetch(); // 输出: Buddy fetches a ball
const cat = new Cat('Whiskers', 'Tabby');
cat.makeSound(); // 输出: Whiskers meows
cat.move(); // 输出: Whiskers moves
cat.climb(); // 输出: Whiskers climbs a tree
// 多态
const animals: Animal[] = [dog, cat];
animals.forEach((animal) => {
  animal.makeSound(); // 调用各自子类的实现
  animal.move();
});

7.2 方法重写与 super 关键字

class Vehicle {
  constructor(
    public brand: string,
    public model: string
  ) {}
  public start(): void {
    console.log(`${this.brand} ${this.model} starts`);
  }
  public drive(): void {
    console.log(`${this.brand} ${this.model} drives`);
  }
}
class Car extends Vehicle {
  constructor(
    brand: string,
    model: string,
    public numberOfDoors: number
  ) {
    super(brand, model);
  }
  // 重写父类方法并调用父类实现
  public start(): void {
    super.start(); // 调用父类的 start 方法
    console.log(`Car with ${this.numberOfDoors} doors is ready`);
  }
  // 新增方法
  public honk(): void {
    console.log(`${this.brand} ${this.model} honks`);
  }
}
// 使用示例
const car = new Car('Toyota', 'Corolla', 4);
car.start(); // 输出: Toyota Corolla starts, Car with 4 doors is ready
car.drive(); // 输出: Toyota Corolla drives
car.honk(); // 输出: Toyota Corolla honks

7.3 多态的应用

interface Shape {
  getArea(): number;
}
class Circle implements Shape {
  constructor(private radius: number) {}
  getArea(): number {
    return Math.PI * this.radius * this.radius;
  }
}
class Rectangle implements Shape {
  constructor(
    private width: number,
    private height: number
  ) {}
  getArea(): number {
    return this.width * this.height;
  }
}
class Triangle implements Shape {
  constructor(
    private base: number,
    private height: number
  ) {}
  getArea(): number {
    return 0.5 * this.base * this.height;
  }
}
// 使用多态
function calculateTotalArea(shapes: Shape[]): number {
  return shapes.reduce((total, shape) => total + shape.getArea(), 0);
}
// 使用示例
const shapes: Shape[] = [new Circle(5), new Rectangle(4, 6), new Triangle(3, 8)];
console.log(`Total area: ${calculateTotalArea(shapes)}`); // 输出: Total area: 78.53981633974483 + 24 + 12 = 114.53981633974483

8. 类的高级特性

8.1 类的混入 (Mixins)

混入是一种在 TypeScript 中实现多重继承的方式,允许我们将多个类的功能组合到一个类中。

// 定义混入
function CanEat<T extends new (...args: any[]) => {}>(Base: T) {
  return class extends Base {
    eat(): void {
      console.log('Eating');
    }
  };
}
function CanSleep<T extends new (...args: any[]) => {}>(Base: T) {
  return class extends Base {
    sleep(): void {
      console.log('Sleeping');
    }
  };
}
// 基础类
class Animal {
  constructor(public name: string) {}
}
// 应用混入
const LivingAnimal = CanSleep(CanEat(Animal));
// 使用示例
const animal = new LivingAnimal('Buddy');
console.log(animal.name); // 输出: Buddy
animal.eat(); // 输出: Eating
animal.sleep(); // 输出: Sleeping

8.2 类的静态工厂方法

静态工厂方法是一种创建类实例的设计模式,提供了一种更灵活的创建对象的方式。

class Person {
  private constructor(
    public name: string,
    public age: number
  ) {}
  // 静态工厂方法
  static createAdult(name: string): Person {
    return new Person(name, 18);
  }
  // 静态工厂方法
  static createChild(name: string, age: number): Person {
    if (age < 18) {
      return new Person(name, age);
    }
    throw new Error('Child must be under 18');
  }
  // 静态工厂方法
  static fromObject(obj: { name: string; age: number }): Person {
    return new Person(obj.name, obj.age);
  }
}
// 使用示例
const adult = Person.createAdult('Alice');
console.log(adult.name, adult.age); // 输出: Alice 18
const child = Person.createChild('Bob', 10);
console.log(child.name, child.age); // 输出: Bob 10
const personFromObject = Person.fromObject({ name: 'Charlie', age: 25 });
console.log(personFromObject.name, personFromObject.age); // 输出: Charlie 25
// 错误示例:私有构造函数不能直接调用
// const person = new Person("Dave", 30); // 编译错误

8.3 类的单例模式

单例模式确保一个类只有一个实例,并提供一个全局访问点。

class Singleton {
  private static instance: Singleton;
  // 私有构造函数
  private constructor() {}
  // 静态方法获取实例
  static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
  public doSomething(): void {
    console.log('Doing something...');
  }
}
// 使用示例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // 输出: true(两个变量引用同一个实例)
instance1.doSomething(); // 输出: Doing something...
instance2.doSomething(); // 输出: Doing something...
// 错误示例:私有构造函数不能直接调用
// const instance = new Singleton(); // 编译错误

9. 最佳实践

9.1 类的设计原则

  • 单一职责原则: 一个类应该只负责一项功能。
  • 开放封闭原则: 类应该对扩展开放,对修改封闭。
  • 里氏替换原则: 子类应该能够替换父类,而不影响程序的正确性。
  • 依赖倒置原则: 依赖于抽象,而不是具体实现。
  • 接口隔离原则: 客户端不应该依赖于它不使用的接口。

9.2 代码风格建议

  • 命名规范: 类名使用 PascalCase,属性和方法使用 camelCase。
  • 访问修饰符: 明确指定访问修饰符,不要依赖默认值。
  • 构造函数: 使用构造函数简写语法,减少样板代码。
  • 方法长度: 保持方法简短,每个方法只负责一项功能。
  • 注释: 为复杂的类和方法添加注释,说明其用途和实现细节。

9.3 性能优化

  • 避免过度继承: 继承层次不宜过深,避免钻石继承问题。
  • 合理使用抽象类: 只在需要强制子类实现特定方法时使用抽象类。
  • 静态成员: 对于不依赖实例状态的方法和属性,使用静态成员。
  • 装饰器性能: 避免在装饰器中执行耗时操作,因为装饰器在类定义时执行。
  • 内存管理: 注意及时释放不再使用的对象,避免内存泄漏。

10. 代码示例

10.1 完整的类实现示例

// 抽象基类
abstract class Vehicle {
  constructor(
    public brand: string,
    public model: string,
    protected year: number
  ) {}
  abstract start(): void;
  abstract stop(): void;
  public getInfo(): string {
    return `${this.brand} ${this.model} (${this.year})`;
  }
  protected getYear(): number {
    return this.year;
  }
}
// 实现类
class Car extends Vehicle {
  constructor(
    brand: string,
    model: string,
    year: number,
    public numberOfDoors: number
  ) {
    super(brand, model, year);
  }
  start(): void {
    console.log(`${this.getInfo()} starts`);
  }
  stop(): void {
    console.log(`${this.getInfo()} stops`);
  }
  public honk(): void {
    console.log(`${this.getInfo()} honks`);
  }
}
class Motorcycle extends Vehicle {
  constructor(
    brand: string,
    model: string,
    year: number,
    public hasSidecar: boolean
  ) {
    super(brand, model, year);
  }
  start(): void {
    console.log(`${this.getInfo()} starts`);
  }
  stop(): void {
    console.log(`${this.getInfo()} stops`);
  }
  public wheelie(): void {
    console.log(`${this.getInfo()} does a wheelie`);
  }
}
// 装饰器
function logVehicle(target: any) {
  const originalConstructor = target;
  function newConstructor(...args: any[]) {
    console.log(`Creating a new ${originalConstructor.name}`);
    return new originalConstructor(...args);
  }
  newConstructor.prototype = originalConstructor.prototype;
  return newConstructor;
}
// 应用装饰器
@logVehicle
class Truck extends Vehicle {
  constructor(
    brand: string,
    model: string,
    year: number,
    public payloadCapacity: number
  ) {
    super(brand, model, year);
  }
  start(): void {
    console.log(`${this.getInfo()} starts`);
  }
  stop(): void {
    console.log(`${this.getInfo()} stops`);
  }
  public loadCargo(weight: number): void {
    if (weight <= this.payloadCapacity) {
      console.log(`${this.getInfo()} loads ${weight}kg of cargo`);
    } else {
      console.log(
        `${this.getInfo()} cannot load ${weight}kg, maximum capacity is ${this.payloadCapacity}kg`
      );
    }
  }
}
// 使用示例
const car = new Car('Toyota', 'Corolla', 2020, 4);
car.start();
car.honk();
car.stop();
console.log(car.getInfo());
const motorcycle = new Motorcycle('Harley-Davidson', 'Sportster', 2019, false);
motorcycle.start();
motorcycle.wheelie();
motorcycle.stop();
console.log(motorcycle.getInfo());
const truck = new Truck('Ford', 'F-150', 2021, 1000);
truck.start();
truck.loadCargo(800);
truck.loadCargo(1200);
truck.stop();
console.log(truck.getInfo());

10.2 装饰器的综合应用

本节示例为实验性装饰器签名((target, key, descriptor),需开启 experimentalDecorators)。迁移到标准装饰器时,把”包装原方法”的逻辑移入 (value, context) 签名并返回新函数即可,参见 6.2.1 节对照。

// 日志装饰器
function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`[${new Date().toISOString()}] ${key} called with:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`[${new Date().toISOString()}] ${key} returned:`, result);
    return result;
  };
  return descriptor;
}
// 缓存装饰器
function cache() {
  const cacheMap = new Map<string, any>();
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const cacheKey = `${key}:${JSON.stringify(args)}`;
      if (cacheMap.has(cacheKey)) {
        console.log(`Cache hit for ${key}`);
        return cacheMap.get(cacheKey);
      }
      console.log(`Cache miss for ${key}`);
      const result = originalMethod.apply(this, args);
      cacheMap.set(cacheKey, result);
      return result;
    };
    return descriptor;
  };
}
// 错误处理装饰器
function handleError(defaultValue: any) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      try {
        return originalMethod.apply(this, args);
      } catch (error) {
        console.error(`Error in ${key}:`, error);
        return defaultValue;
      }
    };
    return descriptor;
  };
}
class Calculator {
  @log
  @cache()
  add(a: number, b: number): number {
    console.log('Performing addition...');
    return a + b;
  }
  @log
  @cache()
  multiply(a: number, b: number): number {
    console.log('Performing multiplication...');
    return a * b;
  }
  @log
  @handleError(0)
  divide(a: number, b: number): number {
    if (b === 0) {
      throw new Error('Division by zero');
    }
    return a / b;
  }
  @log
  @handleError([])
  getNumbers(n: number): number[] {
    if (n < 0) {
      throw new Error('n must be non-negative');
    }
    return Array.from({ length: n }, (_, i) => i);
  }
}
// 使用示例
const calculator = new Calculator();
// 第一次调用(缓存 miss)
console.log(calculator.add(5, 3)); // 输出: 8
// 第二次调用(缓存 hit)
console.log(calculator.add(5, 3)); // 输出: 8
// 不同参数(缓存 miss)
console.log(calculator.add(10, 20)); // 输出: 30
// 乘法
console.log(calculator.multiply(4, 6)); // 输出: 24
console.log(calculator.multiply(4, 6)); // 缓存 hit
// 错误处理 - 除以零
console.log(calculator.divide(10, 0)); // 输出: 0(默认值)
// 错误处理 - 负数
console.log(calculator.getNumbers(-5)); // 输出: [](默认值)
// 正常调用
console.log(calculator.getNumbers(5)); // 输出: [0, 1, 2, 3, 4]

类定义

换行写法:定义基本类 class <类名> { <属性>: <类型> <方法>(<参数>): <返回类型> { <语句> } }

// 定义基本类
class User {
    name: string
    age: number

    greet(): string {
        return `Hello, ${this.name}`
    }
}

基本写法:创建类实例 let <变量> = new <类名>(<参数>)

// 创建类实例
let user = new User()

构造函数

换行写法:定义构造函数 class <类名> { constructor(<参数>: <类型>) { <语句> } }

// 定义构造函数
class User {
    name: string

    constructor(name: string) {
        this.name = name
    }
}

基本写法:构造函数参数简写 class <类名> { constructor(public <属性>: <类型>) {} }

// 构造函数参数简写(自动创建属性)
class User {
    constructor(public name: string, public age: number) {}
}

属性修饰符

换行写法:public 公有属性 class <类名> { public <属性>: <类型> }

// public 公有属性(默认)
class User {
    public name: string = "Alice"
}

换行写法:private 私有属性 class <类名> { private <属性>: <类型> }

// private 私有属性
class User {
    private age: number = 30
}

换行写法:protected 受保护属性 class <类名> { protected <属性>: <类型> }

// protected 受保护属性
class User {
    protected id: number = 1
}

换行写法:readonly 只读属性 class <类名> { readonly <属性>: <类型> }

// readonly 只读属性
class User {
    readonly id: number

    constructor(id: number) {
        this.id = id
    }
}

换行写法:static 静态属性 class <类名> { static <属性>: <类型> }

// static 静态属性
class User {
    static count: number = 0
}

基本写法:访问静态属性 <类名>.<静态属性>

// 访问静态属性
console.log(User.count)

方法

换行写法:定义实例方法 class <类名> { <方法>(<参数>: <类型>): <返回类型> { <语句> } }

// 定义实例方法
class User {
    greet(name: string): string {
        return `Hello, ${name}`
    }
}

换行写法:定义静态方法 class <类名> { static <方法>(<参数>: <类型>): <返回类型> { <语句> } }

// 定义静态方法
class User {
    static create(name: string): User {
        return new User(name)
    }
}

换行写法:定义 getter class <类名> { get <属性>(): <类型> { <语句> } }

// 定义 getter
class User {
    private _name: string = ""

    get name(): string {
        return this._name
    }
}

换行写法:定义 setter class <类名> { set <属性>(<值>: <类型>) { <语句> } }

// 定义 setter
class User {
    private _name: string = ""

    set name(value: string) {
        this._name = value
    }
}

继承

换行写法:类继承 class <子类> extends <父类> { constructor(<参数>) { super(<参数>) } }

// 类继承
class Animal {
    constructor(public name: string) {}
}

class Dog extends Animal {
    constructor(name: string, public breed: string) {
        super(name)
    }
}

换行写法:方法重写 class <子类> extends <父类> { <方法>(<参数>): <返回类型> { <新语句> } }

// 方法重写
class Animal {
    speak(): string {
        return "sound"
    }
}

class Dog extends Animal {
    speak(): string {
        return "Woof!"
    }
}

基本写法:调用父类方法 super.<方法>(<参数>)

// 调用父类方法
class Dog extends Animal {
    speak(): string {
        return `${super.speak()} - Woof!`
    }
}

抽象类

换行写法:定义抽象类 abstract class <类名> { abstract <方法>(<参数>): <返回类型> }

// 定义抽象类
abstract class Animal {
    abstract speak(): string

    eat(): void {
        console.log("eating")
    }
}

换行写法:实现抽象类 class <子类> extends <抽象类> { <方法>(<参数>): <返回类型> { <语句> } }

// 实现抽象类
class Dog extends Animal {
    speak(): string {
        return "Woof!"
    }
}

接口实现

换行写法:类实现接口 interface <接口> { <方法>(<参数>): <返回类型> } class <类名> implements <接口> { <语句> }

// 类实现接口
interface Comparable {
    compare(other: any): number
}

class Number implements Comparable {
    constructor(public value: number) {}

    compare(other: Number): number {
        return this.value - other.value
    }
}

泛型类

换行写法:定义泛型类 class <类名><<T>> { private <属性>: <T>[] <方法>(<参数>: <T>): void { <语句> } }

// 定义泛型类
class Stack<T> {
    private items: T[] = []

    push(item: T): void {
        this.items.push(item)
    }

    pop(): T | undefined {
        return this.items.pop()
    }
}

基本写法:使用泛型类 let <变量> = new <类名><<类型>>()

// 使用泛型类
let stack = new Stack<number>()
stack.push(1)

装饰器

本节及后续”装饰器工厂""类/方法/属性装饰器实战”速查均为实验性装饰器签名(experimentalDecorators: true)。标准装饰器(TS 5.0+ 默认)使用 (value, context) 两参数签名且不含参数装饰器,详见 typescript/280-DecoratorStandardImpl。

换行写法:类装饰器 function <装饰器>(<构造函数>: { new (...args: any[]): any }) { <语句> } @<装饰器> class <类名> { <语句> }

// 类装饰器
function logged<T extends { new (...args: any[]): any }>(constructor: T) {
    return class extends constructor {
        created_at = new Date()
    }
}

@logged
class User {
    constructor(public name: string) {}
}

换行写法:方法装饰器 function <装饰器>(<目标>, <键>, <描述符>) { <语句> } class <类名> { @<装饰器> <方法>(<参数>) { <语句> } }

// 方法装饰器
function log(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value
    descriptor.value = function(...args: any[]) {
        console.log(`调用 ${key}`)
        return original.apply(this, args)
    }
}

class User {
    @log
    greet(name: string): string {
        return `Hello, ${name}`
    }
}

换行写法:属性装饰器 function <装饰器>(<目标>, <键>) { <语句> } class <类名> { @<装饰器> <属性>: <类型> }

// 属性装饰器
function required(target: any, key: string) {
    console.log(`${key} 是必填的`)
}

class User {
    @required
    name: string = ""
}

换行写法:参数装饰器 function <装饰器>(<目标>, <键>, <参数索引>) { <语句> } class <类名> { <方法>(@<装饰器> <参数>: <类型>) { <语句> } }

// 参数装饰器
function log_param(target: any, key: string, index: number) {
    console.log(`参数 ${index} of ${key}`)
}

class User {
    greet(@log_param name: string): string {
        return `Hello, ${name}`
    }
}

装饰器工厂

换行写法:装饰器工厂 function <装饰器>(<参数>): <装饰器> { return function(<目标>) { <语句> } }

// 装饰器工厂
function repeat(times: number) {
    return function(target: any, key: string, descriptor: PropertyDescriptor) {
        const original = descriptor.value
        descriptor.value = function(...args: any[]) {
            for (let i = 0; i < times; i++) {
                original.apply(this, args)
            }
        }
    }
}

class User {
    @repeat(3)
    greet(): void {
        console.log("Hello")
    }
}

访问器

换行写法:使用 getter 和 setter class <类名> { private _<属性>: <类型> get <属性>(): <类型> { return this._<属性> } set <属性>(<值>: <类型>) { this._<属性> = <值> } }

// 使用 getter 和 setter 实现属性访问控制
class User {
    private _age: number = 0

    get age(): number {
        return this._age
    }

    set age(value: number) {
        if (value < 0 || value > 150) {
            throw new Error("Invalid age")
        }
        this._age = value
    }
}

静态块

换行写法:静态初始化块 class <类名> { static <属性>: <类型> static { <语句> } }

// 静态初始化块
class Config {
    static settings: Record<string, string>

    static {
        Config.settings = {
            host: "localhost",
            port: "8080",
        }
    }
}

私有字段

换行写法:使用 # 私有字段 class <类名> { #<属性>: <类型> }

// 使用 # 私有字段(ES2022+)
class User {
    #age: number

    constructor(age: number) {
        this.#age = age
    }

    get_age(): number {
        return this.#age
    }
}

类表达式

基本写法:类表达式 const <变量> = class <类名> { <语句> }

// 类表达式
const User = class {
    constructor(public name: string) {}
}

抽象属性

换行写法:抽象属性 abstract class <类名> { abstract <属性>: <类型> }

// 抽象属性
abstract class Animal {
    abstract name: string

    abstract speak(): string
}

实现多个接口

换行写法:实现多个接口 class <类名> implements <接口1>, <接口2> { <语句> }

// 实现多个接口
interface Comparable {
    compare(other: any): number
}

interface Serializable {
    serialize(): string
}

class User implements Comparable, Serializable {
    compare(other: User): number {
        return 0
    }

    serialize(): string {
        return "User"
    }
}

this 类型

换行写法:使用 this 类型 class <类名> { <方法>(<参数>: <类型>): this { return this } }

// 使用 this 类型实现链式调用
class Calculator {
    private value = 0

    add(n: number): this {
        this.value += n
        return this
    }

    multiply(n: number): this {
        this.value *= n
        return this
    }
}

类与类型

基本写法:类作为类型 let <变量>: <类名> = <实例>

// 类作为类型使用
class User {
    constructor(public name: string) {}
}

let user: User = new User("Alice")

基本写法:使用 typeof 获取构造函数类型 type <别名> = typeof <类名>

// 获取类的构造函数类型
type UserConstructor = typeof User

类装饰器实战

换行写法:使用类装饰器添加属性 function <装饰器><<T> extends { new (...args: any[]): any }>(<构造函数>: <T>) { return class extends <T> { <新属性> } }

// 使用类装饰器添加属性
function timestamp<T extends { new (...args: any[]): any }>(constructor: T) {
    return class extends constructor {
        timestamp = Date.now()
    }
}

@timestamp
class User {
    constructor(public name: string) {}
}

方法装饰器实战

换行写法:使用方法装饰器实现日志 function <装饰器>(<目标>, <键>, <描述符>) { const <原方法> = <描述符>.value <描述符>.value = function(...args) { <前置> return <原方法>.apply(this, args) } }

// 使用方法装饰器实现日志
function log_execution(target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value
    descriptor.value = function(...args: any[]) {
        console.log(`执行 ${key},参数: ${args}`)
        const result = original.apply(this, args)
        console.log(`${key} 执行完成`)
        return result
    }
}

class Calculator {
    @log_execution
    add(a: number, b: number): number {
        return a + b
    }
}

属性装饰器实战

换行写法:使用属性装饰器实现验证 function <装饰器>(<目标>, <键>) { let <值>: <类型> Object.defineProperty(<目标>, <键>, { get, set }) }

// 使用属性装饰器实现验证
function validate_age(target: any, key: string) {
    let value: number

    Object.defineProperty(target, key, {
        get() { return value },
        set(new_value: number) {
            if (new_value < 0 || new_value > 150) {
                throw new Error("Invalid age")
            }
            value = new_value
        }
    })
}

class User {
    @validate_age
    age: number = 0
}