TypeScript 装饰器:类装饰器与方法装饰器
TypeScript 装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问符、属性或参数上。装饰器的本质是一个函数,它可以在运行时修改被装饰对象的行为或元数据。在 Angular、NestJS 等现代框架中,装饰器是核心语法糖。
要开始使用装饰器,必须先配置 TypeScript 编译选项。
1. 启用装饰器支持
在编写代码前,需确保 tsconfig.json 中已开启实验性装饰器支持。
- 打开项目根目录下的
tsconfig.json文件。 - 定位到
compilerOptions节点。 - 添加或修改以下两个字段为
true:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
2. 类装饰器
类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。它接收一个参数:被装饰类的构造函数。
2.1 基础语法与参数
类装饰器函数的定义如下:
function ClassDecorator(target: any) {
// target 是类的构造函数
}
2.2 实战:为类添加静态属性
创建一个名为 CreatedTimestamp 的装饰器,自动为类添加一个 createdAt 属性,记录类被创建的时间。
- 创建一个名为
class-decorator.ts的文件。 - 定义
CreatedTimestamp函数:
function CreatedTimestamp(target: any) {
target.createdAt = new Date('2023-10-01T00:00:00'); // 模拟一个固定时间
}
- 应用装饰器到
UserService类上:
@CreatedTimestamp
class UserService {
constructor(private id: number) {}
}
- 验证结果,直接通过类名访问静态属性:
console.log(UserService.createdAt); // 输出: 2023-10-01T00:00:00
2.3 进阶:覆盖构造函数
如果类装饰器返回一个新的构造函数,它会替换掉原有的类。
- 定义一个
Singleton单例装饰器:
function Singleton(target: any) {
let instance: any;
// 返回一个新的构造函数
return class extends target {
constructor(...args: any[]) {
if (instance) {
return instance;
}
super(...args);
instance = this;
}
};
}
- 应用装饰器并测试单例模式:
@Singleton
class DatabaseConnection {
constructor() {
console.log('初始化数据库连接');
}
}
const db1 = new DatabaseConnection(); // 输出: 初始化数据库连接
const db2 = new DatabaseConnection(); // 无输出
console.log(db1 === db2); // 输出: true
3. 方法装饰器
方法装饰器用于处理类的方法定义。它可以修改方法的属性描述符(PropertyDescriptor),从而改变方法的执行行为(如日志记录、权限验证、防抖)。
3.1 基本语法与参数
方法装饰器接收三个参数:
target:对于静态方法是类的构造函数,对于实例方法是类的原型。propertyKey:方法的名字。descriptor:方法的属性描述符对象。
function MethodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// descriptor.value 包含原始方法体
}
3.2 实战:自动日志记录
创建一个 Log 装饰器,在方法执行前后自动打印参数和返回值。
- 定义
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. 装饰器执行顺序
当一个类中同时存在类装饰器和多个方法装饰器时,它们的执行顺序有着严格的规则。
- 创建包含多个装饰器的测试代码:
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
方法装饰器工厂调用 2
方法装饰器执行 2
方法装饰器执行 1
类装饰器执行
执行规律总结:
- 工厂先行:所有装饰器工厂(外层函数)最先执行,且是从上到下。
- 装饰器后行:所有实际装饰器(内层函数)后执行。
- 方法反向:对于同一个类中的多个方法装饰器,执行顺序是从下往上(即最后声明的方法的装饰器先执行)。
- 类最后:类装饰器总是在所有方法装饰器执行完毕后最后执行。
执行顺序逻辑如下:
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

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