方法装饰器

方法装饰器应用于类的方法,可以修改、替换或观察方法的行为。方法装饰器接收三个参数:目标对象、方法名和属性描述符。

基本语法

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

function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log(`装饰方法: ${propertyKey}`)
}

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

参数说明

方法装饰器的三个参数:

  • target:对于静态成员是类的构造函数,对于实例成员是原型对象
  • propertyKey:方法名
  • descriptor:属性描述符
function inspect(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log("target:", target === Calculator.prototype)
  console.log("propertyKey:", propertyKey)
  console.log("descriptor:", descriptor)
}

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

修改方法行为

方法装饰器可以修改方法的实现。

function log(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value

  descriptor.value = function (...args: any[]) {
    console.log(`调用 ${propertyKey}`)
    console.log(`参数: ${JSON.stringify(args)}`)
    const result = original.apply(this, args)
    console.log(`结果: ${result}`)
    return result
  }

  return descriptor
}

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

const calc = new Calculator()
calc.add(1, 2)

性能监控

方法装饰器可以测量方法执行时间。

function measure(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value

  descriptor.value = function (...args: any[]) {
    const start = performance.now()
    const result = original.apply(this, args)
    const end = performance.now()
    console.log(`${propertyKey} 执行时间: ${(end - start).toFixed(2)}ms`)
    return result
  }

  return descriptor
}

class DataService {
  @measure
  async fetchData(): Promise<any> {
    await new Promise((resolve) => setTimeout(resolve, 100))
    return { data: "数据" }
  }
}

async function main() {
  const service = new DataService()
  await service.fetchData()
}

main()

缓存装饰器

方法装饰器可以实现方法缓存。

function cache(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value
  const cacheMap = new Map<string, any>()

  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args)
    if (cacheMap.has(key)) {
      console.log("从缓存返回")
      return cacheMap.get(key)
    }
    const result = original.apply(this, args)
    cacheMap.set(key, result)
    return result
  }

  return descriptor
}

class MathService {
  @cache
  fibonacci(n: number): number {
    if (n <= 1) return n
    return this.fibonacci(n - 1) + this.fibonacci(n - 2)
  }
}

const service = new MathService()
console.log(service.fibonacci(10))
console.log(service.fibonacci(10))

验证装饰器

方法装饰器可以验证参数。

function validate(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const original = descriptor.value

  descriptor.value = function (...args: any[]) {
    for (const arg of args) {
      if (arg === undefined || arg === null) {
        throw new Error(`${propertyKey} 参数不能为空`)
      }
    }
    return original.apply(this, args)
  }

  return descriptor
}

class UserService {
  @validate
  createUser(name: string, email: string) {
    return { name, email }
  }
}

const service = new UserService()
service.createUser("张三", "test@example.com")
service.createUser("", "test@example.com")

只读方法

方法装饰器可以将方法设为只读。

function readonly(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  descriptor.writable = false
  return descriptor
}

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

const calc = new Calculator()
calc.add = () => 0

装饰器工厂

方法装饰器可以接收参数。

function retry(attempts: number) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const original = descriptor.value

    descriptor.value = async function (...args: any[]) {
      let lastError: any
      for (let i = 0; i < attempts; i++) {
        try {
          return await original.apply(this, args)
        } catch (error) {
          lastError = error
          console.log(`第 ${i + 1} 次尝试失败`)
        }
      }
      throw lastError
    }

    return descriptor
  }
}

class ApiService {
  @retry(3)
  async fetchData(): Promise<any> {
    if (Math.random() > 0.3) {
      throw new Error("请求失败")
    }
    return { data: "成功" }
  }
}

async function main() {
  const api = new ApiService()
  try {
    const result = await api.fetchData()
    console.log(result)
  } catch (error) {
    console.log("所有尝试都失败")
  }
}

main()

小结

方法装饰器应用于类的方法,接收目标对象、方法名和属性描述符三个参数。方法装饰器可以修改方法行为、添加日志、实现缓存、验证参数等。装饰器工厂可以让方法装饰器接收参数。方法装饰器是 AOP(面向切面编程)的重要实现方式。