文章目录

TypeScript 装饰器:类装饰器与方法装饰器

发布于 2026-04-09 02:26:29 · 浏览 2 次 · 评论 0 条

TypeScript 装饰器:类装饰器与方法装饰器

TypeScript 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问符、属性或参数上。装饰器的本质是一个函数,它可以在运行时修改被装饰对象的行为或元数据。在 Angular、NestJS 等现代框架中,装饰器是核心语法糖。

要开始使用装饰器,必须先配置 TypeScript 编译选项。


1. 启用装饰器支持

在编写代码前,需确保 tsconfig.json 中已开启实验性装饰器支持。

  1. 打开项目根目录下的 tsconfig.json 文件。
  2. 定位compilerOptions 节点。
  3. 添加修改以下两个字段为 true
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

2. 类装饰器

类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。它接收一个参数:被装饰类的构造函数。

2.1 基础语法与参数

类装饰器函数的定义如下:

function ClassDecorator(target: any) {
  // target 是类的构造函数
}

2.2 实战:为类添加静态属性

创建一个名为 CreatedTimestamp 的装饰器,自动为类添加一个 createdAt 属性,记录类被创建的时间。

  1. 创建一个名为 class-decorator.ts 的文件。
  2. 定义 CreatedTimestamp 函数:
function CreatedTimestamp(target: any) {
  target.createdAt = new Date('2023-10-01T00:00:00'); // 模拟一个固定时间
}
  1. 应用装饰器到 UserService 类上:
@CreatedTimestamp
class UserService {
  constructor(private id: number) {}
}
  1. 验证结果,直接通过类名访问静态属性:
console.log(UserService.createdAt); // 输出: 2023-10-01T00:00:00

2.3 进阶:覆盖构造函数

如果类装饰器返回一个新的构造函数,它会替换掉原有的类。

  1. 定义一个 Singleton 单例装饰器:
function Singleton(target: any) {
  let instance: any;

  // 返回一个新的构造函数
  return class extends target {
    constructor(...args: any[]) {
      if (instance) {
        return instance;
      }
      super(...args);
      instance = this;
    }
  };
}
  1. 应用装饰器并测试单例模式:
@Singleton
class DatabaseConnection {
  constructor() {
    console.log('初始化数据库连接');
  }
}

const db1 = new DatabaseConnection(); // 输出: 初始化数据库连接
const db2 = new DatabaseConnection(); // 无输出

console.log(db1 === db2); // 输出: true

3. 方法装饰器

方法装饰器用于处理类的方法定义。它可以修改方法的属性描述符(PropertyDescriptor),从而改变方法的执行行为(如日志记录、权限验证、防抖)。

3.1 基本语法与参数

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

  1. target:对于静态方法是类的构造函数,对于实例方法是类的原型。
  2. propertyKey:方法的名字。
  3. descriptor:方法的属性描述符对象。
function MethodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  // descriptor.value 包含原始方法体
}

3.2 实战:自动日志记录

创建一个 Log 装饰器,在方法执行前后自动打印参数和返回值。

  1. 定义 Log 装饰器:
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value; // 保存原方法

  // 重写 descriptor.value
  descriptor.value = function (...args: any[]) {
    console.log(`调用方法: ${propertyKey}`);
    console.log(`参数列表:`, JSON.stringify(args));

    const result = originalMethod.apply(this, args); // 执行原方法

    console.log(`返回值:`, result);
    return result;
  };
}
```

2.  **应用**装饰器到 `Calculator` 类的 `add` 方法:

```typescript
class Calculator {
  @Log
  add(a: number, b: number): number {
    return a + b;
  }
}
```

3.  **实例化**并调用方法进行测试:

```typescript
const calc = new Calculator();
calc.add(10, 20);
```

执行后控制台将输出:
```
调用方法: add
参数列表: [10,20]
返回值: 30
```

#### 3.3 执行流程可视化

方法装饰器通过修改 `descriptor` 来包裹原逻辑,其执行流程如下:

```mermaid
graph LR
    A[调用方法] --> B{Method Decorator?}
    B -->|否| C[执行原方法]
    B -->|是| D[执行 wrapper 函数]
    D --> E[预处理: 验证/日志]
    E --> F[调用原方法 originalMethod.apply]
    F --> G[后处理: 格式化/计算耗时]
    G --> H[返回结果]
    C --> H
```

---

### 4. 装饰器工厂

有时我们需要向装饰器传递参数(例如 `@Log('info')` 而不是 `@Log`)。这需要使用装饰器工厂。装饰器工厂是一个返回装饰器函数的函数。

1.  **定义** `Logger` 工厂函数,接收日志前缀参数:

```typescript
function Logger(prefix: string) {
  // 返回真正的装饰器
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      console.log(`[${prefix}] ${propertyKey} 开始执行`);
      const result = originalMethod.apply(this, args);
      console.log(`[${prefix}] ${propertyKey} 执行结束`);
      return result;
    };
  };
}
```

2.  **应用**带参数的装饰器:

```typescript
class PaymentService {
  @Logger('Payment')
  processPayment(amount: number) {
    console.log(`处理金额: ${amount}`);
  }
}

const payment = new PaymentService();
payment.processPayment(100);

控制台输出:

[Payment] processPayment 开始执行
处理金额: 100
[Payment] processPayment 执行结束

5. 类装饰器与方法装饰器对比

为了更清晰地理解两者的区别,请参考下表:

特性 类装饰器 方法装饰器
作用目标 类本身(构造函数) 类的方法(静态或实例)
参数列表 (target: Function) (target: any, propertyKey: string, descriptor: PropertyDescriptor)
主要用途 继承扩展、单例模式、添加静态属性 日志记录、权限校验、性能监控、修改方法行为
返回值影响 返回构造函数可替换类定义 返回 PropertyDescriptor 可覆盖方法属性
应用场景示例 @Component, @Injectable @Get, @Post, @Debounce

6. 装饰器执行顺序

当一个类中同时存在类装饰器和多个方法装饰器时,它们的执行顺序有着严格的规则。

  1. 创建包含多个装饰器的测试代码:
function ClassDecorator() {
  console.log('类装饰器工厂调用');
  return function (target: any) {
    console.log('类装饰器执行');
  };
}

function MethodDecorator(id: number) {
  console.log(`方法装饰器工厂调用 ${id}`);
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(`方法装饰器执行 ${id}`);
  };
}

@classDecorator()
class Example {
  @MethodDecorator(1)
  method1() {}

  @MethodDecorator(2)
  method2() {}
}
  1. 观察控制台输出结果:
类装饰器工厂调用
方法装饰器工厂调用 1
方法装饰器工厂调用 2
方法装饰器执行 2
方法装饰器执行 1
类装饰器执行

执行规律总结

  1. 工厂先行:所有装饰器工厂(外层函数)最先执行,且是从上到下。
  2. 装饰器后行:所有实际装饰器(内层函数)后执行。
  3. 方法反向:对于同一个类中的多个方法装饰器,执行顺序是从下往上(即最后声明的方法的装饰器先执行)。
  4. 类最后:类装饰器总是在所有方法装饰器执行完毕后最后执行。

执行顺序逻辑如下:

graph TD subgraph "代码声明顺序 (从上到下)" A[Class Factory] B[Method1 Factory] C[Method2 Factory] end subgraph "实际执行顺序" A --> D[Class Factory Exec] B --> E[Method1 Factory Exec] C --> F[Method2 Factory Exec] F --> G[Method2 Decorator Exec] G --> H[Method1 Decorator Exec] H --> I[Class Decorator Exec] end style D fill:#f9f,stroke:#333,stroke-width:2px style E fill:#ccf,stroke:#333,stroke-width:2px style F fill:#ccf,stroke:#333,stroke-width:2px style G fill:#ccf,stroke:#333,stroke-width:2px style H fill:#ccf,stroke:#333,stroke-width:2px style I fill:#f9f,stroke:#333,stroke-width:2px

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文