# 定义

装饰器(Decorator)是一种特殊的语法,用于修改类、方法、属性或参数的行为。装饰器在很多语言中都有支持,JavaScript 在 ES6 中也引入了类装饰器,并在 TypeScript 中得到了广泛使用。装饰器语法可以增强类或其成员的功能,使代码更具可维护性和可扩展性。

装饰器目前属于【实验性语法】,因此在项目中使用可能需要用到对应插件进行解析、转换

# 基本语法

装饰器的基本形式是一个函数,它接收目标对象、属性名称等参数,并可以修改目标或属性的行为。

# 类装饰器

类装饰器是作用于整个类的,它接收类的构造函数作为参数,并可以对类进行修改或扩展。

function LogClass(target: Function) {
  console.log(`Class decorated: ${target.name}`);
}
@LogClass
class MyClass {
  constructor() {
    console.log("MyClass instantiated");
  }
}
// 输出:Class decorated: MyClass

# 方法装饰器

方法装饰器用于修改类中方法的行为。它接收三个参数:目标对象、属性名称以及方法的属性描述符。

function LogMethod(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Method ${propertyKey} called with args: ${args}`);
    return originalMethod.apply(this, args);
  };
}
class MyClass {
  @LogMethod
  sayHello(name: string) {
    console.log(`Hello, ${name}`);
  }
}
const myClass = new MyClass();
myClass.sayHello("John");
// 输出:Method sayHello called with args: John
// 输出:Hello, John

# 属性装饰器

属性装饰器用于修改类属性的行为,它接收目标对象和属性名称作为参数。

function LogProperty(target: any, propertyKey: string) {
  let value = target[propertyKey];
  const getter = () => {
    console.log(`Get value of ${propertyKey}: ${value}`);
    return value;
  };
  const setter = (newValue: any) => {
    console.log(`Set value of ${propertyKey}: ${newValue}`);
    value = newValue;
  };
  Object.defineProperty(target, propertyKey, {
    get: getter,
    set: setter,
    enumerable: true,
    configurable: true,
  });
}
class MyClass {
  @LogProperty
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
const myClass = new MyClass("Alice");
// 输出:Set value of name: Alice
myClass.name = "Bob";
// 输出:Set value of name: Bob
console.log(myClass.name);
// 输出:Get value of name: Bob
// 输出:Bob

# 参数装饰器

参数装饰器用于修改方法参数的行为,接收目标对象、方法名称和参数索引作为参数。

function LogParameter(
  target: any,
  propertyKey: string,
  parameterIndex: number
) {
  console.log(
    `Parameter ${parameterIndex} in method ${propertyKey} is being decorated`
  );
}
class MyClass {
  greet(@LogParameter name: string) {
    console.log(`Hello, ${name}`);
  }
}
// 输出:Parameter 0 in method greet is being decorated

# 访问装饰器

访问器装饰器可以装饰类的 getter 和 setter,用法类似于方法装饰器。

function LogAccessor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalGet = descriptor.get;
  descriptor.get = function () {
    console.log(`Getting value of ${propertyKey}`);
    return originalGet?.apply(this);
  };
}
class MyClass {
  private _value: string = "default";
  @LogAccessor
  get value() {
    return this._value;
  }
  set value(newValue: string) {
    this._value = newValue;
  }
}
const myClass = new MyClass();
console.log(myClass.value);
// 输出:Getting value of value
// 输出:default

# TperScript 装饰器配置

为了在 TypeScript 中使用装饰器,需要在 tsconfig.json 中启用装饰器相关配置:

{
  "compilerOptions": {
    "experimentalDecorators": true, // 启用装饰器语法
    "emitDecoratorMetadata": true // 启用元数据反射
  }
}

# 装饰器组合

多个装饰器可以叠加在一起使用,执行顺序为从下到上:

function First() {
  console.log("First Decorator");
  return function (target: any) {
    console.log("First decorator executed");
  };
}
function Second() {
  console.log("Second Decorator");
  return function (target: any) {
    console.log("Second decorator executed");
  };
}
@First()
@Second()
class MyClass {}

# 实际应用场景

  • 日志记录:可以通过装饰器自动记录类、方法的调用和参数。
  • 权限检查:在执行方法前检查权限,或者验证用户是否有权限执行某些操作。
  • 缓存:为某些方法结果添加缓存逻辑,减少不必要的重复计算。
  • 依赖注入:在 Angular、NestJS 等框架中,装饰器常用于依赖注入,自动为类的构造函数注入依赖项。