Skip to content

装饰器(Decorator)

reflect-metadata

基本概念

reflect-metadata 是实现元编程的库,提供元数据反射API,可以在运行时检查和操作对象的元数据。

元数据(Metadata)是描述数据的数据,在 TypeScript 中,元数据可以附加到类、方法、属性等上,用于在运行时获取类型信息、配置信息等。

安装和导入

bash
npm install reflect-metadata
typescript
import 'reflect-metadata'

主要 API

1. 定义元数据

装饰器方式 - @Reflect.metadata

typescript
class PersonClass {
  @Reflect.metadata('methodName', 'methodValue')
  method() {
    console.log('method');
  }
}

直接定义 - Reflect.defineMetadata

typescript
const person = new PersonClass('property');
// 语法:Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey?)
Reflect.defineMetadata('name', 'fan', person, 'property');

💡 @Reflect.metadataReflect.defineMetadata 的语法糖,用于简化元数据定义过程。

2. 检查元数据

判断是否存在元数据

typescript
// 语法:Reflect.hasMetadata(metadataKey, target, propertyKey?)
const hasMetadata = Reflect.hasMetadata('name', person, 'property');
console.log('判断是否有元数据:', hasMetadata); // true

3. 获取元数据

获取元数据(包括继承的)

typescript
// 语法:Reflect.getMetadata(metadataKey, target, propertyKey?)
const metadata = Reflect.getMetadata('name', person, 'property');
console.log('获取元数据:', metadata); // 'fan'

获取自有元数据(不包括继承的)

typescript
// 语法:Reflect.getOwnMetadata(metadataKey, target, propertyKey?)
const ownMetadata = Reflect.getOwnMetadata('name', person, 'property');
console.log('获取自有元数据:', ownMetadata); // 'fan'

// 获取方法上的元数据需要通过原型
const methodMetadata = Reflect.getOwnMetadata('methodName', PersonClass.prototype, 'method');
console.log('方法元数据:', methodMetadata); // 'methodValue'

4. 删除元数据

typescript
// 语法:Reflect.deleteMetadata(metadataKey, target, propertyKey?)
Reflect.deleteMetadata('name', person, 'property');
const hasMetadataAfterDelete = Reflect.hasMetadata('name', person, 'property');
console.log('删除后是否存在:', hasMetadataAfterDelete); // false

其他常用 API

获取元数据键列表

typescript
// 获取所有元数据键(包括继承的)
const metadataKeys = Reflect.getMetadataKeys(target, propertyKey);

// 获取自有元数据键(不包括继承的)
const ownMetadataKeys = Reflect.getOwnMetadataKeys(target, propertyKey);

获取所有元数据

typescript
// 获取所有元数据键值对
const allMetadata = Reflect.getOwnMetadata(Symbol.for('custom:annotations'), target);

实际应用场景

1. 依赖注入

typescript
@Injectable()
class UserService {
  constructor(
    @Inject('DATABASE') private db: Database
  ) {}
}

2. 路由装饰器

typescript
@Controller('/users')
class UserController {
  @Get('/:id')
  @UseGuards(AuthGuard)
  getUserById(@Param('id') id: string) {
    // ...
  }
}

3. 验证装饰器

typescript
class CreateUserDto {
  @IsString()
  @Length(2, 20)
  name: string;

  @IsEmail()
  email: string;
}

注意事项

  1. target 参数

    • 对于实例属性:使用实例对象
    • 对于静态属性:使用类构造函数
    • 对于方法:使用类的原型 Class.prototype
  2. 继承关系

    • getMetadata 会查找继承链上的元数据
    • getOwnMetadata 只查找对象自身的元数据
  3. 性能考虑:元数据操作有一定性能开销,避免在热点代码中频繁使用

在 NestJS 中的应用

NestJS 大量使用 reflect-metadata 来实现:

  • 装饰器元数据存储
  • 依赖注入容器
  • 路由元数据管理
  • 管道、守卫、拦截器的元数据

类装饰器

类装饰器是应用于类构造函数的特殊函数,可以用来监视、修改或替换类的定义。类装饰器在类声明之前被声明,紧挨着类声明。

基本语法

类装饰器是一个函数,接收一个参数:类的构造函数。

typescript
function logClass(constructor: Function) {
  console.log(`Class Created`, constructor.name);
}

@logClass
class HttpClient {
  constructor() {
    console.log('HttpClient constructor');
  }
}

new HttpClient();
// 输出:
// Class Created HttpClient
// HttpClient constructor

装饰器工厂

装饰器工厂是返回装饰器函数的高阶函数,可以接收参数来控制装饰器的行为。

typescript
function logClassWithParams(params: string) {
  return function (constructor: Function) {
    console.log(`Class Created`, constructor.name, params);
  }
}

@logClassWithParams('https://www.xyu.fan')
class HttpClient2 {
  constructor() {
    console.log('HttpClient2 constructor');
  }
}

new HttpClient2();
// 输出:
// Class Created HttpClient2 https://www.xyu.fan
// HttpClient2 constructor

扩展类功能

类装饰器可以向类的原型添加新的属性和方法。

typescript
function addProperty(target: any) {
  target.prototype.url = 'https://www.xyu.fan';
}

// 使用接口声明合并来提供类型支持
interface HttpClient3 {
  url: string;
}

@addProperty
class HttpClient3 {
  constructor() {
    console.log('HttpClient3 constructor');
  }
}

const httpClient3 = new HttpClient3();
console.log(httpClient3.url); // 输出:https://www.xyu.fan

重写类构造函数

类装饰器可以返回一个新的构造函数来替换原有的构造函数。

typescript
function rewriteClass<T extends { new (...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    constructor(...args: any[]) {
      super(...args);
      console.log('rewriteClass constructor');
    }
  }
}

@rewriteClass
class HttpClient4 {
  constructor() {
    console.log('HttpClient4 constructor');
  }
}

new HttpClient4();
// 输出:
// HttpClient4 constructor
// rewriteClass constructor

高级用法示例

1. 单例模式装饰器

typescript
function Singleton<T extends { new (...args: any[]): {} }>(constructor: T) {
  let instance: T;
  return class extends constructor {
    constructor(...args: any[]) {
      if (instance) {
        return instance;
      }
      super(...args);
      instance = this as any;
      return instance;
    }
  } as T;
}

@Singleton
class DatabaseConnection {
  constructor(public connectionString: string) {}
}

2. 日志装饰器

typescript
function Logger(logLevel: 'info' | 'debug' | 'error' = 'info') {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
      constructor(...args: any[]) {
        console.log(`[${logLevel.toUpperCase()}] Creating instance of ${constructor.name}`);
        super(...args);
        console.log(`[${logLevel.toUpperCase()}] Instance of ${constructor.name} created`);
      }
    };
  };
}

@Logger('debug')
class ApiService {
  constructor(private baseUrl: string) {}
}

3. 验证装饰器

typescript
function ValidateConstructor(validationRules: Record<string, (value: any) => boolean>) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
      constructor(...args: any[]) {
        // 验证构造函数参数
        Object.keys(validationRules).forEach((key, index) => {
          const validator = validationRules[key];
          if (!validator(args[index])) {
            throw new Error(`Validation failed for parameter ${key}`);
          }
        });
        super(...args);
      }
    };
  };
}

@ValidateConstructor({
  email: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  age: (value: number) => value > 0 && value < 150
})
class User {
  constructor(public email: string, public age: number) {}
}

多个装饰器的执行顺序

当多个装饰器应用于同一个类时,它们的执行顺序是从下到上(最接近类声明的先执行)。

typescript
function first() {
  console.log("first(): factory evaluated");
  return function (target: any) {
    console.log("first(): called");
  };
}

function second() {
  console.log("second(): factory evaluated");
  return function (target: any) {
    console.log("second(): called");
  };
}

@first()
@second()
class ExampleClass {}

// 输出:
// first(): factory evaluated
// second(): factory evaluated
// second(): called
// first(): called

方法装饰器

方法装饰器是应用于方法的装饰器,可以用来监视、修改或替换方法的定义。方法装饰器在方法声明之前被声明。

基本语法

方法装饰器是一个函数,接收三个参数:

  • target:类的原型对象(对于静态方法是类的构造函数)
  • propertyKey:方法的名称
  • descriptor:方法的属性描述符
typescript
function logMethod(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Method ${propertyKey} called with arguments:`, args);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned:`, result);
    return result;
  }
}

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

const calculator = new Calculator();
calculator.add(1, 2);
// 输出:
// Method add called with arguments: [1, 2]
// Method add returned: 3

权限控制装饰器

方法装饰器可以用来实现权限检查,在方法执行前进行权限验证。

typescript
function checkPermission(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    if (user[args[0]].role.includes('admin')) {
      return originalMethod.apply(this, args);
    } else {
      throw new Error('Permission denied');
    }
  }
  return descriptor;
}

const user = {
  'user1': { role: ['admin', 'user'] },
  'user2': { role: ['user'] },
}

class WebSite {
  @checkPermission
  deleteUser(userId: string) {
    console.log(`delete user ${userId}`);
  }
}

const webSite = new WebSite();
webSite.deleteUser('user1'); // 成功执行
// webSite.deleteUser('user2'); // 抛出 Permission denied 错误

缓存装饰器

方法装饰器可以实现方法结果的缓存,提高性能。

typescript
function cacheMethod(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const cache = new Map();
  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log(`Cache hit for ${key}`);
      return cache.get(key);
    }
    const result = originalMethod.apply(this, args);
    console.log(`Cache ${key} = ${result}`);
    cache.set(key, result);
    return result;
  }
  return descriptor;
}

class CacheCalculator {
  @cacheMethod
  add(a: number, b: number) {
    return a + b;
  }
}

const cacheCalculator = new CacheCalculator();
cacheCalculator.add(1, 2); // 首次调用,缓存结果
cacheCalculator.add(1, 2); // 第二次调用,从缓存返回

高级用法示例

1. 性能监控装饰器

typescript
function performance(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`Method ${propertyKey} executed in ${end - start} milliseconds`);
    return result;
  };
  return descriptor;
}

class DataProcessor {
  @performance
  processLargeData(data: any[]) {
    // 模拟耗时操作
    return data.map(item => item * 2);
  }
}

2. 重试装饰器

typescript
function retry(maxAttempts: number = 3, delay: number = 1000) {
  return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = async function (...args: any[]) {
      let attempts = 0;
      while (attempts < maxAttempts) {
        try {
          return await originalMethod.apply(this, args);
        } catch (error) {
          attempts++;
          if (attempts >= maxAttempts) {
            throw error;
          }
          console.log(`Attempt ${attempts} failed, retrying in ${delay}ms...`);
          await new Promise(resolve => setTimeout(resolve, delay));
        }
      }
    };
    return descriptor;
  };
}

class ApiClient {
  @retry(3, 2000)
  async fetchData(url: string) {
    // 模拟可能失败的 API 调用
    if (Math.random() < 0.7) {
      throw new Error('Network error');
    }
    return { data: 'success' };
  }
}

3. 参数验证装饰器

typescript
function validateArgs(validators: ((arg: any) => boolean)[]) {
  return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      validators.forEach((validator, index) => {
        if (!validator(args[index])) {
          throw new Error(`Invalid argument at position ${index} for method ${propertyKey}`);
        }
      });
      return originalMethod.apply(this, args);
    };
    return descriptor;
  };
}

class MathOperations {
  @validateArgs([
    (a: number) => typeof a === 'number' && !isNaN(a),
    (b: number) => typeof b === 'number' && !isNaN(b) && b !== 0
  ])
  divide(a: number, b: number) {
    return a / b;
  }
}

4. 异步错误处理装饰器

typescript
function asyncErrorHandler(target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = async function (...args: any[]) {
    try {
      return await originalMethod.apply(this, args);
    } catch (error) {
      console.error(`Error in method ${propertyKey}:`, error);
      // 可以在这里添加错误上报、日志记录等逻辑
      throw error;
    }
  };
  return descriptor;
}

class UserService {
  @asyncErrorHandler
  async createUser(userData: any) {
    // 可能抛出异常的异步操作
    return await database.save(userData);
  }
}

在 NestJS 中的应用

1. HTTP 方法装饰器

typescript
@Controller('users')
export class UsersController {
  @Get(':id')
  findOne(@Param('id') id: string) {
    return `This action returns user #${id}`;
  }

  @Post()
  @HttpCode(201)
  create(@Body() createUserDto: CreateUserDto) {
    return 'This action adds a new user';
  }

  @Put(':id')
  update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
    return `This action updates user #${id}`;
  }

  @Delete(':id')
  remove(@Param('id') id: string) {
    return `This action removes user #${id}`;
  }
}

2. 守卫装饰器

typescript
@Controller('admin')
export class AdminController {
  @Get('users')
  @UseGuards(AuthGuard, RolesGuard)
  @Roles('admin')
  getAllUsers() {
    return 'Admin only endpoint';
  }
}

3. 拦截器装饰器

typescript
@Controller('data')
export class DataController {
  @Get()
  @UseInterceptors(LoggingInterceptor, TransformInterceptor)
  @Timeout(5000)
  getData() {
    return { message: 'Hello World' };
  }
}

4. 管道装饰器

typescript
@Controller('validation')
export class ValidationController {
  @Post()
  create(
    @Body(ValidationPipe) createDto: CreateUserDto,
    @Query('type', ParseIntPipe) type: number
  ) {
    return 'Data validated and processed';
  }
}

PropertyDescriptor 详解

方法装饰器中的 descriptor 参数包含以下属性:

typescript
interface PropertyDescriptor {
  value?: any;           // 方法的实现
  writable?: boolean;    // 是否可写
  enumerable?: boolean;  // 是否可枚举
  configurable?: boolean; // 是否可配置
  get?(): any;          // getter 方法
  set?(v: any): void;   // setter 方法
}

装饰器组合使用

多个方法装饰器可以组合使用,执行顺序从下到上:

typescript
class ApiService {
  @asyncErrorHandler
  @performance
  @retry(3)
  @cacheMethod
  async fetchUserData(userId: string) {
    return await this.httpClient.get(`/users/${userId}`);
  }
}

常见应用模式

1. AOP(面向切面编程)

typescript
// 前置通知
function before(advice: Function) {
  return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      advice.apply(this, args);
      return originalMethod.apply(this, args);
    };
  };
}

// 后置通知
function after(advice: Function) {
  return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const result = originalMethod.apply(this, args);
      advice.call(this, result);
      return result;
    };
  };
}

2. 方法限流装饰器

typescript
function throttle(limit: number, window: number) {
  const calls = new Map();
  return function (target: Object, propertyKey: string | Symbol, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
      const now = Date.now();
      const key = `${target.constructor.name}.${propertyKey}`;
      
      if (!calls.has(key)) {
        calls.set(key, []);
      }
      
      const callTimes = calls.get(key);
      const recentCalls = callTimes.filter((time: number) => now - time < window);
      
      if (recentCalls.length >= limit) {
        throw new Error(`Method ${propertyKey} called too frequently`);
      }
      
      recentCalls.push(now);
      calls.set(key, recentCalls);
      
      return originalMethod.apply(this, args);
    };
  };
}

访问器装饰器和属性装饰器

访问器装饰器

访问器装饰器用来装饰类的 getter 和 setter 方法,可以监视、修改或替换访问器的定义。

基本语法

访问器装饰器接收三个参数:

  • target:类的原型对象(静态成员为类的构造函数)
  • propertyKey:属性的名称
  • descriptor:属性描述符
typescript
function LogAccessor(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalGet = descriptor.get;
  descriptor.get = function () {
    console.log(`Accessed property: ${propertyKey}`);
    return originalGet?.call(this);
  };
}

class User {
  private _name = 'Alice';

  @LogAccessor
  get name() {
    return this._name;
  }
}

const u = new User();
console.log(u.name); 
// 输出:
// Accessed property: name
// Alice

高级访问器装饰器示例

1. 读写监控装饰器

typescript
function Monitor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalGet = descriptor.get;
  const originalSet = descriptor.set;
  
  if (originalGet) {
    descriptor.get = function () {
      console.log(`Reading ${propertyKey}`);
      const value = originalGet.call(this);
      console.log(`Read ${propertyKey}: ${value}`);
      return value;
    };
  }
  
  if (originalSet) {
    descriptor.set = function (value: any) {
      console.log(`Writing ${propertyKey}: ${value}`);
      return originalSet.call(this, value);
    };
  }
}

class Product {
  private _price: number = 0;

  @Monitor
  get price() {
    return this._price;
  }

  set price(value: number) {
    this._price = value;
  }
}

2. 数据验证装饰器

typescript
function ValidateRange(min: number, max: number) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalSet = descriptor.set;
    descriptor.set = function (value: number) {
      if (value < min || value > max) {
        throw new Error(`${propertyKey} must be between ${min} and ${max}`);
      }
      return originalSet?.call(this, value);
    };
  };
}

class Temperature {
  private _celsius: number = 0;

  @ValidateRange(-273.15, 1000)
  get celsius() {
    return this._celsius;
  }

  set celsius(value: number) {
    this._celsius = value;
  }
}

属性装饰器

属性装饰器用来装饰类的属性,通常与 reflect-metadata 结合使用来存储元数据。

基本语法

属性装饰器接收两个参数:

  • target:类的原型对象(静态属性为类的构造函数)
  • propertyKey:属性的名称
typescript
import 'reflect-metadata';

function Required(target: any, propertyKey: string) {
  const requiredProps = Reflect.getMetadata('required:props', target) || [];
  Reflect.defineMetadata(
    'required:props',
    [...requiredProps, propertyKey],
    target
  );
}

class User1 {
  @Required
  username: string;

  email: string;
}

function validateRequired(obj: any) {
  const requiredProps = Reflect.getMetadata('required:props', obj) || [];
  for (const key of requiredProps) {
    if (!obj[key]) {
      console.log(`${key} is required`);
    }
  }
}

const u1 = new User1();
u1.username = ''; // 未赋值
validateRequired(u1); // 输出:username is required

高级属性装饰器示例

1. 类型验证装饰器

typescript
function Type(type: 'string' | 'number' | 'boolean' | 'object') {
  return function (target: any, propertyKey: string) {
    Reflect.defineMetadata('validation:type', type, target, propertyKey);
  };
}

function validate(obj: any) {
  const properties = Object.getOwnPropertyNames(obj);
  for (const prop of properties) {
    const expectedType = Reflect.getMetadata('validation:type', obj, prop);
    if (expectedType && typeof obj[prop] !== expectedType) {
      throw new Error(`Property ${prop} must be of type ${expectedType}`);
    }
  }
}

class UserProfile {
  @Type('string')
  name: string;

  @Type('number')
  age: number;

  @Type('boolean')
  isActive: boolean;
}

2. 格式化装饰器

typescript
function Format(formatter: (value: any) => any) {
  return function (target: any, propertyKey: string) {
    const formatters = Reflect.getMetadata('formatters', target) || new Map();
    formatters.set(propertyKey, formatter);
    Reflect.defineMetadata('formatters', formatters, target);
    
    // 重新定义属性
    let value: any;
    Object.defineProperty(target, `_${propertyKey}`, {
      writable: true,
      enumerable: false
    });
    
    Object.defineProperty(target, propertyKey, {
      get() {
        return this[`_${propertyKey}`];
      },
      set(newValue: any) {
        this[`_${propertyKey}`] = formatter(newValue);
      },
      enumerable: true,
      configurable: true
    });
  };
}

class UserInfo {
  @Format((value: string) => value.toLowerCase().trim())
  email: string;

  @Format((value: string) => value.charAt(0).toUpperCase() + value.slice(1).toLowerCase())
  firstName: string;
}

const userInfo = new UserInfo();
userInfo.email = '  JOHN@EXAMPLE.COM  ';
userInfo.firstName = 'jOhN';
console.log(userInfo.email);     // john@example.com
console.log(userInfo.firstName); // John

3. 默认值装饰器

typescript
function Default(defaultValue: any) {
  return function (target: any, propertyKey: string) {
    const defaults = Reflect.getMetadata('defaults', target) || new Map();
    defaults.set(propertyKey, defaultValue);
    Reflect.defineMetadata('defaults', defaults, target);
  };
}

function applyDefaults(obj: any) {
  const defaults = Reflect.getMetadata('defaults', obj) || new Map();
  for (const [key, value] of defaults) {
    if (obj[key] === undefined || obj[key] === null) {
      obj[key] = value;
    }
  }
}

class Configuration {
  @Default('localhost')
  host: string;

  @Default(3000)
  port: number;

  @Default(false)
  debug: boolean;
}

装饰器执行顺序

根据代码示例,不同类型装饰器的执行顺序如下:

typescript
function classDecorator1(target) { console.log('classDecorator1'); }
function classDecorator2(target) { console.log('classDecorator2'); }
function propertyDecorator1(target, propertyKey) { console.log('propertyDecorator1'); }
function propertyDecorator2(target, propertyKey) { console.log('propertyDecorator2'); }
function methodDecorator1(target, propertyKey) { console.log('methodDecorator1'); }
function methodDecorator2(target, propertyKey) { console.log('methodDecorator2'); }
function accessorDecorator1(target, propertyKey) { console.log('accessorDecorator1'); }
function accessorDecorator2(target, propertyKey) { console.log('accessorDecorator2'); }
function parameterDecorator1(target, propertyKey, parameterIndex: number) {
  console.log('parameterDecorator1', propertyKey);
}
function parameterDecorator2(target, propertyKey, parameterIndex: number) {
  console.log('parameterDecorator2', propertyKey);
}

@classDecorator1
@classDecorator2
class Example {
  @accessorDecorator1
  @accessorDecorator2
  get myProp() {
    return this.prop;
  }

  @propertyDecorator1
  @propertyDecorator2
  prop: string;

  @methodDecorator1
  @methodDecorator2
  method(
    @parameterDecorator2 @parameterDecorator1 param1: any,
    @parameterDecorator2 @parameterDecorator1 param2: any
  ) {}
}

// 执行顺序:
// 1. 访问器装饰器:accessorDecorator2 → accessorDecorator1
// 2. 属性装饰器:propertyDecorator2 → propertyDecorator1  
// 3. 参数装饰器:从右到左,从下到上
// 4. 方法装饰器:methodDecorator2 → methodDecorator1
// 5. 类装饰器:classDecorator2 → classDecorator1

执行顺序规则总结

  1. 属性装饰器、方法装饰器、访问器装饰器:按照在类中出现的顺序,从上往下依次执行
  2. 类装饰器:最后执行
  3. 参数装饰器:先于方法装饰器执行
  4. 多个装饰器:从右向左,从下到上执行(最接近被装饰项的先执行)

在 NestJS 中的应用

1. 属性装饰器应用

typescript
// 依赖注入
class UserService {
  @Inject('USER_REPOSITORY')
  private userRepository: UserRepository;

  @InjectRepository(User)
  private userRepo: Repository<User>;
}

// 配置注入
class AppService {
  @ConfigProperty('database.host')
  private dbHost: string;

  @ConfigProperty('database.port', 5432)
  private dbPort: number;
}

2. 访问器装饰器应用

typescript
// 数据转换
class UserDto {
  private _email: string;

  @Transform(({ value }) => value.toLowerCase())
  get email() {
    return this._email;
  }

  set email(value: string) {
    this._email = value;
  }
}

// API 属性文档
class CreateUserDto {
  private _age: number;

  @ApiProperty({ minimum: 0, maximum: 120 })
  @IsNumber()
  @Min(0)
  @Max(120)
  get age() {
    return this._age;
  }

  set age(value: number) {
    this._age = value;
  }
}

实用装饰器集合

1. 只读属性装饰器

typescript
function ReadOnly(target: any, propertyKey: string) {
  Object.defineProperty(target, propertyKey, {
    writable: false,
    configurable: false
  });
}

class Config {
  @ReadOnly
  readonly API_URL = 'https://api.example.com';
}

2. 延迟加载装饰器

typescript
function Lazy(loader: () => any) {
  return function (target: any, propertyKey: string) {
    let loaded = false;
    let value: any;
    
    Object.defineProperty(target, propertyKey, {
      get() {
        if (!loaded) {
          value = loader();
          loaded = true;
        }
        return value;
      },
      enumerable: true,
      configurable: true
    });
  };
}

class DataService {
  @Lazy(() => new ExpensiveResource())
  resource: ExpensiveResource;
}

3. 观察者模式装饰器

typescript
function Observable(target: any, propertyKey: string) {
  const observers = new Set<(value: any) => void>();
  let value: any;
  
  // 添加订阅方法
  target[`subscribe${propertyKey.charAt(0).toUpperCase() + propertyKey.slice(1)}`] = 
    function (observer: (value: any) => void) {
      observers.add(observer);
    };
  
  Object.defineProperty(target, propertyKey, {
    get() {
      return value;
    },
    set(newValue: any) {
      const oldValue = value;
      value = newValue;
      observers.forEach(observer => observer({ oldValue, newValue, propertyKey }));
    },
    enumerable: true,
    configurable: true
  });
}

class Model {
  @Observable
  name: string;
}

const model = new Model();
model.subscribeName((change) => {
  console.log(`Property ${change.propertyKey} changed from ${change.oldValue} to ${change.newValue}`);
});
model.name = 'John'; // 触发观察者

参数装饰器

参数装饰器用来装饰函数参数,通常与 reflect-metadata 结合使用。

基本语法

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

  • target:类的原型对象
  • propertyKey:方法的名称
  • parameterIndex:参数在函数参数列表中的索引
typescript
function LogParam(target: any, propertyKey: string, parameterIndex: number) {
  const existingParams = Reflect.getMetadata('log:params', target, propertyKey) || [];
  existingParams.push(parameterIndex);
  Reflect.defineMetadata('log:params', existingParams, target, propertyKey);
}

function logParams(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  const loggedParams = Reflect.getMetadata('log:params', target, propertyKey) || [];
  
  descriptor.value = function (...args: any[]) {
    loggedParams.forEach((index: number) => {
      console.log(`Parameter ${index} in ${propertyKey}:`, args[index]);
    });
    return originalMethod.apply(this, args);
  };
}

class ApiService {
  @logParams
  createUser(@LogParam name: string, @LogParam email: string, age: number) {
    return { name, email, age };
  }
}

装饰器执行顺序详解

基于代码示例,完整的执行顺序如下:

typescript
@classDecorator1
@classDecorator2
class Example {
  @accessorDecorator1
  @accessorDecorator2
  get myProp() {
    return this.prop;
  }

  @propertyDecorator1
  @propertyDecorator2
  prop: string;

  @methodDecorator1
  @methodDecorator2
  method(
    @parameterDecorator4 @parameterDecorator3 param1: any,
    @parameterDecorator2 @parameterDecorator1 param2: any
  ) {}
}

// 实际执行顺序:
// 1. accessorDecorator2
// 2. accessorDecorator1
// 3. propertyDecorator2
// 4. propertyDecorator1
// 5. parameterDecorator3 (param1)
// 6. parameterDecorator4 (param1)
// 7. parameterDecorator1 (param2)
// 8. parameterDecorator2 (param2)
// 9. methodDecorator2
// 10. methodDecorator1
// 11. classDecorator2
// 12. classDecorator1

执行规则总结

  1. 同类型多个装饰器:从下到上执行(最接近被装饰项的先执行)
  2. 不同类型装饰器
    • 访问器/属性/方法装饰器:按在类中出现的顺序执行
    • 参数装饰器:先于对应的方法装饰器执行
    • 类装饰器:最后执行
  3. 参数装饰器特殊规则
    • 多个参数:从右向左执行
    • 单个参数多个装饰器:从右向左执行

Released under the MIT License.