装饰器

169 阅读6分钟

问题:装饰器是什么意思

装饰器在编程中是一种特殊的声明,它允许你在不修改原有代码的情况下,向现有的对象或方法添加新的行为或属性。在 JavaScript 和 TypeScript 中,装饰器是一种高级语法,用于修改类、方法、访问器、属性或参数。

装饰器的意义

  1. 声明式编程:装饰器提供了一种声明式的方式来添加或修改功能,使得代码更加清晰易读。
  2. 代码复用:通过装饰器,可以轻松复用相同的功能,减少代码重复。
  3. 分离关注点:装饰器有助于将不同的功能(比如日志、验证等)从业务逻辑中分离出来,提高代码的可维护性。

装饰器的原理

装饰器本质上是函数,但它们通过一种特殊的语法应用到类、方法等上。当你在代码中使用装饰器时,实际上是在执行一个函数,这个函数可以修改或扩展其装饰的目标的行为。

  1. 类装饰器:在类定义时被调用,接收构造函数作为其唯一的参数。
  2. 方法装饰器:在方法定义时被调用,接收三个参数:目标对象、方法的名称和属性描述符。
  3. 属性装饰器:在属性定义时被调用,接收两个参数:目标对象和属性的名称。
  4. 参数装饰器:在方法参数定义时被调用,接收三个参数:目标对象、方法的名称和参数在参数列表中的索引。

示例

假设有一个日志装饰器,用于记录方法的调用信息:

typescriptCopy code
function Log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyName} with arguments:`, args);
    return originalMethod.apply(this, args);
  };
}

class MyClass {
  @Log
  myMethod(arg1: number, arg2: string) {
    console.log('Executing myMethod');
  }
}

在这个示例中,Log 装饰器修改了 myMethod 方法,使其在被调用时打印出日志。这是通过替换方法的描述符的 value(即方法本身)来实现的。

总结

装饰器是一种强大的模式,允许开发者以声明式和优雅的方式扩展和定制代码的行为。它们在 Angular、NestJS 等现代 JavaScript 框架中广泛应用,提高了代码的表达力和可维护性。

问题:如何自定义装饰器

在 TypeScript 和像 NestJS 这样的框架中,自定义装饰器是一个强大的特性,允许你向类、方法或属性添加额外的行为或标记。装饰器本质上是一种特殊类型的函数。以下是创建不同类型的自定义装饰器的基本步骤:

类装饰器

类装饰器用于观察、修改或替换类定义。一个类装饰器只接受一个参数:

  • constructor: 被装饰的类的构造函数。
typescriptCopy code
function MyClassDecorator(constructor: Function) {
  console.log('MyClassDecorator called on:', constructor);
}

@MyClassDecorator
class MyClass { }

方法装饰器

方法装饰器用于观察、修改或替换类方法的定义。一个方法装饰器接受三个参数:

  • target: 被装饰的方法所属的类的原型对象。
  • propertyKey: 方法的名称。
  • descriptor: 方法的属性描述符。
typescriptCopy code
function MyMethodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('MyMethodDecorator called on:', target, propertyKey, descriptor);
}

class MyClass {
  @MyMethodDecorator
  myMethod() { }
}

属性装饰器

属性装饰器用于观察、修改或替换类属性的定义。一个属性装饰器接受两个参数:

  • target: 被装饰的属性所属的类的原型对象。
  • propertyKey: 属性的名称。
typescriptCopy code
function MyPropertyDecorator(target: any, propertyKey: string) {
  console.log('MyPropertyDecorator called on:', target, propertyKey);
}

class MyClass {
  @MyPropertyDecorator
  myProperty: string;
}

参数装饰器

参数装饰器用于观察、修改或替换类方法的参数。一个参数装饰器接受三个参数:

  • target: 被装饰的参数所属的类的原型对象。
  • propertyKey: 参数所属方法的名称。
  • parameterIndex: 参数在参数列表中的索引。
typescriptCopy code
function MyParameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
  console.log('MyParameterDecorator called on:', target, propertyKey, parameterIndex);
}

class MyClass {
  myMethod(@MyParameterDecorator param: string) { }
}

注意事项

  • 在 TypeScript 中使用装饰器时,需要在 tsconfig.json 中启用装饰器:设置 "experimentalDecorators": true
  • 装饰器提供了元编程的能力,它们在 Angular、NestJS 等框架中广泛使用,用于定义元数据、增强类的行为等。
  • 装饰器可以是工厂函数,允许传递参数来定制行为。例如:@MyDecorator(someArgs)。在这种情况下,你需要返回一个正确签名的装饰器函数。

通过上述示例,你可以创建自定义装饰器来增强你的 TypeScript 应用的功能。

问题:调用时机?

在代码执行中,装饰器的调用顺序确实很重要。装饰器通常在定义时就被调用,而不是在运行时调用。这意味着装饰器会在相关代码(如类、方法或属性)首次被 JavaScript 引擎解析时执行,而不是在实例化类或调用方法时执行。我们来具体看一下不同类型的装饰器的调用时机:

  1. 类装饰器

    • 类装饰器在类定义被解析时调用。这是在任何实例被创建之前,甚至在任何静态方法或属性被访问之前。
    • 例如,如果你定义了一个类装饰器,并将其应用于一个类,那么这个装饰器会在你的代码中的任何地方创建该类的实例之前被调用。
  2. 方法、访问器和属性装饰器

    • 这些装饰器在它们所装饰的方法、访问器或属性首次被 JavaScript 引擎解析时调用。
    • 它们在运行时不会被再次调用,除非装饰器本身返回一个新的函数来替换原始方法(这种情况下,这个返回的函数在每次方法调用时执行)。
  3. 参数装饰器

    • 参数装饰器在方法参数被解析时调用,也是在任何代码调用这个方法之前。

举例说明

假设我们有一个类装饰器 @ClassDecorator,一个方法装饰器 @MethodDecorator,和一个参数装饰器 @ParamDecorator,它们都简单地输出一些信息:

typescriptCopy code
@ClassDecorator
class MyClass {
  @MethodDecorator
  myMethod(@ParamDecorator param: string) {
    console.log("Running method");
  }
}

// 创建 MyClass 的实例
const myClassInstance = new MyClass();
// 调用方法
myClassInstance.myMethod("hello");

在这个例子中,装饰器的调用顺序是:

  1. @ClassDecoratorMyClass 类定义被解析时调用。
  2. @MethodDecoratormyMethod 方法定义被解析时调用。
  3. @ParamDecoratormyMethod 的参数被解析时调用。

这些都发生在 MyClass 的实例被创建和 myMethod 被调用之前。

总之,装饰器提供了一种在代码定义阶段注入行为的方式,而不是在运行阶段。这是它们强大的元编程特性的一部分,允许开发者在保持代码整洁和模块化的同时,添加额外的功能。