01. 装饰器是什么
装饰器是一种特殊的声明,可以附加到类、方法、属性、参数上。它让你在不修改原始代码的基础上给类或方法加额外行为。
装饰器本质上是个函数——接收被装饰的目标和相关信息,然后返回或修改目标。
装饰器的执行时机:类定义时执行(不是实例化时)。也就是说装饰器在代码加载阶段就跑完了。
注意:装饰器在 TypeScript 里还是实验特性——需要 tsconfig.json 里开启 experimentalDecorators: true。TC39 的 ES 装饰器提案跟 TS 的实验性装饰器有一定差异,未来会统一。
装饰器在 NestJS(后端)、Angular(前端)、class-validator(数据校验)等框架里大量使用。
json
// tsconfig.json 里
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}02. 类装饰器
类装饰器接收一个参数——被装饰的类的构造函数。返回一个扩展或替换原类的新构造函数(或者返回 undefined 原类不变)。
常见用途:注册组件(Angular 的 @Component)、注册模块(NestJS 的 @Module)、给类加元数据、自动混入方法。
类装饰器不能用箭头函数(因为箭头函数没有 prototype),需要普通函数声明。
配合 reflect-metadata 库,装饰器可以给类附加元数据,然后在运行时通过反射读取。这是 NestJS 依赖注入的底层机制。
typescript
// 类装饰器
function Logger(constructor: Function) {
console.log(`Class ${constructor.name} is being created`);
}
@Logger
class UserService {
// 类定义时控制台输出:Class UserService is being created
}
// 返回新类(工厂模式)
function WithTimestamp<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
createdAt = new Date();
};
}
// @WithTimestamp
// class Post {
// title: string;
// }03. 方法装饰器
方法装饰器接收三个参数:类的 prototype(实例方法)或构造函数(静态方法)、方法名、属性描述符。
方法装饰器可以修改属性的行为——包装原有方法增加日志、计时、权限检查等横切关注点。这本质上是 AOP(面向切面编程)的思想。
方法装饰器里可以拿到原方法并替换它——在装饰器里用 descriptor.value 拿到原函数,替换成新的包装函数。
常见用途:@Log 记录方法调用、@Catch 捕获异常、@Debounce 防抖、@Throttle 节流、@Authorize 权限检查。
typescript
// 方法装饰器——日志
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
const result = original.apply(this, args);
console.log(`${propertyKey} returned`, result);
return result;
};
}
class Calculator {
@Log
add(a: number, b: number) {
return a + b;
}
}
// const calc = new Calculator();
// calc.add(1, 2);
// 输出:Calling add with [1, 2]
// add returned 304. 属性装饰器与参数装饰器
属性装饰器接收两个参数:类的 prototype 和属性名。它没有属性描述符(因为属性还没赋值),返回值被忽略。
属性装饰器不能直接修改属性值,但可以通过 Reflect.defineMetadata 给属性存入元数据。这是 class-validator 和 class-transformer 的工作原理。
参数装饰器接收三个参数:类的 prototype、方法名、参数位置(从 0 开始)。主要用于依赖注入框架识别需要注入的参数。
装饰器工厂——装饰器本身是函数,装饰器工厂是返回装饰器的函数。比如 @Get('/users') 的 Get 是一个工厂函数,它返回实际的装饰器。这使得装饰器可以接受配置参数。
typescript
// 装饰器工厂
function Route(path: string) {
return function(target: any, propertyKey: string) {
Reflect.defineMetadata('path', path, target, propertyKey);
};
}
class UserController {
@Route('/users')
getUsers() {}
}
// 属性装饰器——存元数据
function MinLength(length: number) {
return function(target: any, propertyKey: string) {
Reflect.defineMetadata('minLength', length, target, propertyKey);
};
}
class User {
@MinLength(3)
name: string;
}装饰器工厂的名字通常首字母大写(@Get、@Post),以便跟普通装饰器区分。工厂让你给每个装饰器传不同的配置。
05. 装饰器的实际框架应用
NestJS——把所有东西都用装饰器串联:@Module、@Controller、@Injectable、@Get、@Post、@Body、@Param、@Query。整个框架就是装饰器的集合。
class-validator——用装饰器做运行时校验:@IsString()、@IsEmail()、@Min(1)、@Max(100)。
class-transformer——用装饰器控制序列化/反序列化:@Expose()、@Exclude()、@Transform()。
TypeORM——用装饰器定义数据库实体:@Entity()、@Column()、@PrimaryGeneratedColumn()、@ManyToOne()。
这些框架的原理都一样——装饰器存元数据,框架在运行时读取元数据构建行为。
typescript
// NestJS 风格示例
@Controller('users')
class UserController {
constructor(private userService: UserService) {}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.userService.findById(id);
}
@Post()
async create(@Body() data: CreateUserDto) {
return this.userService.create(data);
}
}
// class-validator 示例
class CreateUserDto {
@IsString()
@MinLength(3)
name: string;
@IsEmail()
email: string;
@IsInt()
@Min(0)
@Max(150)
age: number;
}装饰器加 reflect-metadata 是 TypeScript 依赖注入的基石。理解它们是读懂 NestJS、Angular 源码的关键。
知识测验
第 1/5 题正确 0
装饰器是什么时候执行的?
下一节
下一节 TS 项目实战