第 10 章:装饰器
学习目标
- 理解装饰器的本质(类/方法包装函数)
- 学会类装饰器、方法装饰器、属性装饰器
- 掌握参数装饰器与元数据反射
- 识别装饰器的使用限制
一、装饰器是什么
装饰器是一个函数,用来包装类、方法、属性等。
typescript
function log(target: any): void {
console.log(`类被装饰:${target.name}`);
}
@log
class User {}
// 输出:类被装饰:User⚠️ 坑 1:装饰器是实验性特性,
tsconfig.json必须开启experimentalDecorators: true。
二、类装饰器
typescript
// 装饰器函数:接收类的构造函数
function sealed(constructor: Function): void {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class User {
constructor(public name: string) {}
}装饰器工厂(返回装饰器):
typescript
function log(prefix: string) {
return function (constructor: Function): void {
console.log(`${prefix}: ${constructor.name} 被创建`);
};
}
@log('INFO')
class User {}
// INFO: User 被创建三、方法装饰器
typescript
function measure(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
): void {
const original = descriptor.value;
descriptor.value = function (...args: any[]): any {
const start = Date.now();
const result = original.apply(this, args);
const end = Date.now();
console.log(`${propertyKey} 耗时:${end - start}ms`);
return result;
};
}
class Calculator {
@measure
add(a: number, b: number): number {
return a + b;
}
}
new Calculator().add(1, 2); // add 耗时:0ms(打印)⚠️ 坑 2:方法装饰器拿到的
descriptor.value是原方法,必须在descriptor.value上改写才生效,直接return装饰器函数不行。
四、属性装饰器
typescript
function format(format: string) {
return function (target: any, propertyKey: string): void {
let value: string;
Object.defineProperty(target, propertyKey, {
get(): string { return value; },
set(v: string): void {
value = format === 'upper' ? v.toUpperCase() : v.toLowerCase();
},
enumerable: true,
configurable: true,
});
};
}
class User {
@format('upper')
name!: string;
}
const u = new User();
u.name = 'tom';
console.log(u.name); // 'TOM'⚠️ 坑 3:属性装饰器不能直接修改原型上的属性(原型链共享),只能用
Object.defineProperty在实例上定义。
五、参数装饰器
typescript
function requirePositive(target: any, propertyKey: string, parameterIndex: number): void {
console.log(`${propertyKey} 的第 ${parameterIndex} 个参数需要正数`);
}
class Account {
deposit(@requirePositive amount: number): void {
console.log(`存入:${amount}`);
}
}六、装饰器执行顺序
多个装饰器由下往上包装,执行从上到下:
typescript
function first() { return (target: any) => console.log('first'); }
function second() { return (target: any) => console.log('second'); }
@first()
@second()
class User {}
// 输出:first → second七、实战:权限装饰器
typescript
function requireRole(role: 'admin' | 'user') {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor,
): void {
const original = descriptor.value;
descriptor.value = function (...args: any[]): any {
const current = (this as any).role;
if (current !== role) throw new Error(`需要 ${role} 权限`);
return original.apply(this, args);
};
};
}
class AdminController {
role: 'admin' = 'admin';
@requireRole('admin')
deleteUser(id: number): void {
console.log(`删除用户 ${id}`);
}
}
new AdminController().deleteUser(1); // ✅八、与 reflect-metadata 配合
装饰器常配合 reflect-metadata 库(在 NestJS / Angular 里大量使用):
typescript
import 'reflect-metadata';
function Column(type: string) {
return function (target: any, propertyKey: string): void {
Reflect.defineMetadata('design:type', type, target, propertyKey);
};
}
class User {
@Column('string')
name!: string;
}
const type = Reflect.getMetadata('design:type', new User(), 'name'); // 'string'九、本章小结
| 要点 | 关键 |
|---|---|
| 装饰器 | 包装类/方法/属性的函数,实验性语法 |
| 开启 | experimentalDecorators: true |
| 类装饰器 | 接收类的构造函数 |
| 方法装饰器 | 改写 descriptor.value |
| 属性装饰器 | 用 Object.defineProperty |
| 工厂 | 返回装饰器的高阶函数 |
| 顺序 | 由下往上包装,从上往下执行 |
动手练习
- 类装饰器:写
@logClass装饰器,类被实例化时打印类名 - 方法装饰器:写
@deprecated装饰器,调用方法时打印"该方法即将废弃" - 权限装饰器:写
@requireRole('admin'),权限不足抛错
下一章:第 11 章:类型推导 →