ToolkitX
知识库工具箱

装饰器

类装饰器、方法装饰器、属性装饰器

20min·高级

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 3

04. 属性装饰器与参数装饰器

属性装饰器接收两个参数:类的 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 项目实战

下一节