阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 装饰器基础与应用

装饰器基础与应用

作者:陈川 阅读数:22068人阅读 分类: TypeScript

装饰器是TypeScript中一种强大的语法特性,允许在不修改原始代码的情况下,通过注解的方式扩展类、方法、属性或参数的行为。它们广泛应用于日志记录、性能分析、依赖注入等场景,能够显著提升代码的可维护性和复用性。

装饰器的基本概念

装饰器本质上是一个函数,它接收特定的参数并返回一个新的函数或修改后的目标。TypeScript中的装饰器分为以下几种类型:

  1. 类装饰器
  2. 方法装饰器
  3. 属性装饰器
  4. 参数装饰器
  5. 访问器装饰器

要使用装饰器,需要在tsconfig.json中启用实验性装饰器支持:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

类装饰器

类装饰器应用于类构造函数,可以用来观察、修改或替换类定义。它接收一个参数:类的构造函数。

function logClass(target: Function) {
  console.log(`Class ${target.name} is defined`);
}

@logClass
class MyClass {
  constructor() {
    console.log('Creating instance');
  }
}

// 输出: "Class MyClass is defined"

更实用的例子是给类添加元数据:

function addMetadata(metadata: object) {
  return function(target: Function) {
    Reflect.defineMetadata('custom:metadata', metadata, target);
  };
}

@addMetadata({ version: '1.0.0', author: 'John Doe' })
class ApiService {
  // ...
}

const metadata = Reflect.getMetadata('custom:metadata', ApiService);
console.log(metadata); // { version: '1.0.0', author: 'John Doe' }

方法装饰器

方法装饰器用于类的方法,可以拦截方法调用、修改方法行为或添加额外功能。它接收三个参数:

  1. 对于静态成员是类的构造函数,对于实例成员是类的原型
  2. 成员名称
  3. 成员的属性描述符
function logMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function(...args: any[]) {
    console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
    const result = originalMethod.apply(this, args);
    console.log(`Method ${propertyKey} returned: ${result}`);
    return result;
  };
  
  return descriptor;
}

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

const calc = new Calculator();
calc.add(2, 3);
// 输出:
// "Calling add with args: [2,3]"
// "Method add returned: 5"

属性装饰器

属性装饰器应用于类的属性,可以用来修改属性的行为或添加元数据。它接收两个参数:

  1. 对于静态成员是类的构造函数,对于实例成员是类的原型
  2. 成员名称
function formatDate(formatString: string) {
  return function(target: any, propertyKey: string) {
    let value: Date;
    
    const getter = function() {
      return value.toLocaleDateString('en-US', { 
        year: 'numeric', 
        month: 'long', 
        day: 'numeric' 
      });
    };
    
    const setter = function(newVal: Date) {
      value = newVal;
    };
    
    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

class Event {
  @formatDate('MMMM Do YYYY')
  date: Date;
  
  constructor(date: Date) {
    this.date = date;
  }
}

const event = new Event(new Date());
console.log(event.date); // "June 15, 2023" (格式取决于当前日期)

参数装饰器

参数装饰器用于类构造函数或方法的参数,通常用于依赖注入或参数验证。它接收三个参数:

  1. 对于静态成员是类的构造函数,对于实例成员是类的原型
  2. 成员名称(如果是构造函数参数则为undefined)
  3. 参数在函数参数列表中的索引
function validateParam(min: number, max: number) {
  return function(target: any, propertyKey: string, parameterIndex: number) {
    const existingValidations: any[] = Reflect.getOwnMetadata('validations', target, propertyKey) || [];
    existingValidations.push({
      index: parameterIndex,
      min,
      max
    });
    Reflect.defineMetadata('validations', existingValidations, target, propertyKey);
  };
}

function validate(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  const validations = Reflect.getOwnMetadata('validations', target, propertyKey) || [];
  
  descriptor.value = function(...args: any[]) {
    for (const validation of validations) {
      const arg = args[validation.index];
      if (arg < validation.min || arg > validation.max) {
        throw new Error(`Parameter at index ${validation.index} must be between ${validation.min} and ${validation.max}`);
      }
    }
    return originalMethod.apply(this, args);
  };
  
  return descriptor;
}

class MathOperations {
  @validate
  divide(
    @validateParam(1, 100) dividend: number,
    @validateParam(1, 10) divisor: number
  ): number {
    return dividend / divisor;
  }
}

const math = new MathOperations();
console.log(math.divide(50, 5)); // 10
console.log(math.divide(0, 5)); // 抛出错误

装饰器工厂

装饰器工厂是一个返回装饰器函数的函数,允许通过参数配置装饰器行为。

function logExecutionTime(threshold: number = 0) {
  return function(
    target: any,
    propertyKey: string,
    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();
      const duration = end - start;
      
      if (duration > threshold) {
        console.warn(`Method ${propertyKey} took ${duration.toFixed(2)}ms to execute (threshold: ${threshold}ms)`);
      }
      
      return result;
    };
    
    return descriptor;
  };
}

class DataProcessor {
  @logExecutionTime(10)
  processLargeData() {
    // 模拟耗时操作
    for (let i = 0; i < 1000000; i++) {
      Math.sqrt(i);
    }
  }
}

const processor = new DataProcessor();
processor.processLargeData(); // 如果执行时间超过10ms会输出警告

装饰器组合

多个装饰器可以同时应用于同一个目标,它们会按照特定顺序执行:

  1. 参数装饰器 → 方法装饰器 → 访问器装饰器 → 属性装饰器 → 类装饰器
  2. 同一类型的装饰器,从下到上执行
function first() {
  console.log('first(): factory evaluated');
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('first(): called');
  };
}

function second() {
  console.log('second(): factory evaluated');
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('second(): called');
  };
}

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

// 输出顺序:
// "second(): factory evaluated"
// "first(): factory evaluated"
// "first(): called"
// "second(): called"

实际应用场景

1. API路由注册

const routes: any[] = [];

function Controller(prefix: string = '') {
  return function(target: Function) {
    Reflect.defineMetadata('prefix', prefix, target);
    
    if (!Reflect.hasMetadata('routes', target)) {
      Reflect.defineMetadata('routes', [], target);
    }
    
    routes.push(target);
  };
}

function Get(path: string = '') {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const routes = Reflect.getMetadata('routes', target.constructor) || [];
    
    routes.push({
      method: 'get',
      path,
      handler: propertyKey
    });
    
    Reflect.defineMetadata('routes', routes, target.constructor);
  };
}

@Controller('/users')
class UserController {
  @Get('/')
  getAll() {
    return 'All users';
  }
  
  @Get('/:id')
  getById(id: string) {
    return `User ${id}`;
  }
}

// 可以遍历routes数组来注册实际的路由
routes.forEach(controller => {
  const prefix = Reflect.getMetadata('prefix', controller);
  const controllerRoutes = Reflect.getMetadata('routes', controller);
  
  controllerRoutes.forEach((route: any) => {
    console.log(`Registering route: ${route.method.toUpperCase()} ${prefix}${route.path}`);
    // 实际应用中这里会调用框架的路由注册方法
  });
});

2. 表单验证

interface ValidationRule {
  type: string;
  message: string;
  validator?: (value: any) => boolean;
}

function Validator(rules: ValidationRule[]) {
  return function(target: any, propertyKey: string) {
    Reflect.defineMetadata('validation', rules, target, propertyKey);
  };
}

function validateForm(target: any) {
  const properties = Object.getOwnPropertyNames(target);
  
  properties.forEach(property => {
    const rules = Reflect.getMetadata('validation', target, property) as ValidationRule[];
    if (!rules) return;
    
    const value = target[property];
    
    for (const rule of rules) {
      if (rule.type === 'required' && (value === undefined || value === null || value === '')) {
        throw new Error(rule.message);
      }
      
      if (rule.validator && !rule.validator(value)) {
        throw new Error(rule.message);
      }
    }
  });
}

class UserForm {
  @Validator([
    { type: 'required', message: 'Username is required' },
    { 
      type: 'length', 
      message: 'Username must be between 3 and 20 characters',
      validator: (value: string) => value.length >= 3 && value.length <= 20
    }
  ])
  username: string;
  
  @Validator([
    { type: 'required', message: 'Email is required' },
    { 
      type: 'pattern', 
      message: 'Invalid email format',
      validator: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
    }
  ])
  email: string;
  
  constructor(data: Partial<UserForm>) {
    Object.assign(this, data);
  }
  
  validate() {
    validateForm(this);
  }
}

const form = new UserForm({
  username: 'johndoe',
  email: 'john@example.com'
});
form.validate(); // 验证通过

const invalidForm = new UserForm({
  username: 'jo',
  email: 'invalid-email'
});
try {
  invalidForm.validate(); // 抛出验证错误
} catch (error) {
  console.error(error.message);
}

3. 性能监控

function trackPerformance(metricName: string) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    
    descriptor.value = async function(...args: any[]) {
      const startMark = `${propertyKey}-start`;
      const endMark = `${propertyKey}-end`;
      
      performance.mark(startMark);
      try {
        const result = await originalMethod.apply(this, args);
        return result;
      } finally {
        performance.mark(endMark);
        performance.measure(metricName, startMark, endMark);
        
        const measures = performance.getEntriesByName(metricName);
        const lastMeasure = measures[measures.length - 1];
        
        console.log(`[Performance] ${metricName}: ${lastMeasure.duration.toFixed(2)}ms`);
        
        // 可以在这里将性能数据发送到监控系统
      }
    };
    
    return descriptor;
  };
}

class AnalyticsService {
  @trackPerformance('fetchAnalyticsData')
  async fetchData() {
    // 模拟API调用
    await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
    return { data: 'sample' };
  }
}

const analytics = new AnalyticsService();
analytics.fetchData(); // 输出性能数据

装饰器与元数据反射

TypeScript的装饰器可以与反射元数据API结合使用,实现更强大的功能。需要安装reflect-metadata包:

npm install reflect-metadata

然后在应用入口导入:

import 'reflect-metadata';

示例:依赖注入容器

type Constructor<T = any> = new (...args: any[]) => T;

const Injectable = (): ClassDecorator => target => {};

class Container {
  private instances = new Map<Constructor, any>();
  
  register<T>(token: Constructor<T>, instance: T) {
    this.instances.set(token, instance);
  }
  
  resolve<T>(token: Constructor<T>): T {
    if (this.instances.has(token)) {
      return this.instances.get(token);
    }
    
    const params = Reflect.getMetadata('design:paramtypes', token) || [];
    const injections = params.map(param => this.resolve(param));
    
    const instance = new token(...injections);
    this.instances.set(token, instance);
    
    return instance;
  }
}

@Injectable()
class DatabaseService {
  connect() {
    console.log('Connected to database');
  }
}

@Injectable()
class UserRepository {
  constructor(private database: DatabaseService) {}
  
  getUsers() {
    this.database.connect();
    return ['user1', 'user2'];
  }
}

const container = new Container();
const userRepo = container.resolve(UserRepository);
console.log(userRepo.getUsers()); // ["user1", "user2"]

装饰器的局限性

虽然装饰器功能强大,但也存在一些限制和注意事项:

  1. 执行顺序:多个装饰器应用于同一目标时,执行顺序可能不符合直觉
  2. 调试困难:装饰器修改了原始行为,可能导致调试困难
  3. 性能影响:复杂的装饰器逻辑可能影响性能
  4. 类型信息:装饰器可能使类型系统复杂化,需要额外处理类型信息
  5. 提案阶段:装饰器目前仍是ECMAScript提案,未来可能有变化

高级技巧

动态装饰器应用

function conditionalDecorator(condition: boolean, decorator: MethodDecorator) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    if (condition) {
      return decorator(target, propertyKey, descriptor);
    }
    return descriptor;
  };
}

class FeatureToggle {
  @conditionalDecorator(
    process.env.NODE_ENV === 'development',
    logMethod
  )
  experimentalMethod() {
    // 只在开发环境记录日志
  }
}

装饰器组合工具

function composeDecorators(...decorators: MethodDecorator[]) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    return decorators.reduce((currentDescriptor, decorator) => {
      return decorator(target, propertyKey, currentDescriptor) || currentDescriptor;
    }, descriptor);
  };
}

class MultiDecorated {
  @composeDecorators(
    logMethod,
    trackPerformance('importantOperation')
  )
  importantOperation() {
    // 同时应用日志和性能跟踪
  }
}

装饰器与泛型

function typeCheck<T>() {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    
    descriptor.value = function(arg: T) {
      if (typeof arg !== typeof {} as T) {
        throw new Error(`Invalid type: expected ${typeof {} as T}, got ${typeof arg}`);
      }
      return originalMethod.call(this, arg);
    };
    
    return descriptor;
  };
}

class GenericExample {
  @typeCheck<number>()
  processNumber(num: number) {
    return num * 2;
  }
}

const example = new GenericExample();
console.log(example.processNumber(5)); // 10
console.log(example.processNumber('5' as any)); // 抛出类型错误

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:参数属性

下一篇:混入(Mixins)模式实现

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌