Skip to content

装饰器 ​

2023-10-07 11:20:15

定义 ​

装饰器是一种语法结构,用来在定义类时修改类的一些行为

在语法上,装饰器有如下几个特征

  • 第一个字符(或者说前缀)是@,后面是一个表达式
  • @后面的表达式,必须是一个函数(或者执行后可以得到一个函数)
  • 这个函数接受所修饰对象的一些相关值作为参数
  • 这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象
ts
function decorator(value, context) {
  console.log(`hello this is ${context.kind} ${context.name}`)
}

@decorator
class Person {} // "hello this is class Person"

如上代码,类 Person 在执行前会先执行装饰器 decorator,并且会向装饰器自动传入参数

装饰器的结构 ​

装饰器的函数定义类型

ts
type Decorator = (
  value: DecoratedValue,
  context: {
    kind: string
    name: string | symbol
    addInitializer?(initializer: () => void): void
    static?: boolean
    private?: boolean
    access: {
      get?(): unknown
      set?(value: unknown): void
    }
  }
) => void | ReplacementValue

上面代码中,Decorator 是装饰器的类型定义。它是一个函数,使用时会接收到 value 和 context 两个参数。

  • value:所装饰的对象。
  • context:上下文对象,TypeScript 提供一个原生接口 ClassMethodDecoratorContext,描述这个对象。

所以定义装饰器的时候类型可以写成这样

ts
function decorator(value: any, context: ClassMethodDecoratorContext) {
  console.log(`hello this is ${context.kind} ${context.name}`)
}

context 对象的属性会根据所装饰对象的不同而不同,其中有两个属性(kind 和 name)是必有的

  • kind:字符串,表示所装饰对象的类型,一共有六种类型的装饰器

    • class
    • method
    • getter
    • setter
    • field
    • accessor
  • name:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等

  • addInitializer():函数,用来添加类的初始化逻辑。之前这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入 addInitializer()方法。注意,addInitializer()没有返回值。

  • private:布尔值,表示所装饰的对象是否为类的私有成员

  • static:布尔值,表示所装饰的对象是否为类的静态成员

  • access:一个对象,包含了某个值的 get 和 set 方法

使用 ​

这里类装饰器返回一个函数,替代当前类的构造方法

ts
function countInstances(value: any, context: any) {
  let instanceCount = 0

  const wrapper = function (...args: any[]) {
    instanceCount++
    const instance = new value(...args)
    instance.count = instanceCount
    return instance
  } as unknown as typeof MyClass

  wrapper.prototype = value.prototype
  return wrapper
}

@countInstances
class MyClass {}

const inst1 = new MyClass()
const inst2 = new MyClass()
inst1 instanceof MyClass // true
inst1.count // 1
inst2.count // 2

上面代码中,类装饰器 @countInstances 返回一个函数,替换了类 MyClass 的构造方法。新的构造方法实现了实例的计数,每新建一个实例,计数器就会加一,并且对实例添加 count 属性,用来表示当前实例的编号。

注意,为了确保新构造方法继承定义在 MyClass 的原型之上的成员,加入第11行的代码,确保两者的原型对象是一致的。否则,新的构造函数 wrapper 的原型对象,与 MyClass 不同,无法通过 instanceof 运算符。

Released under the MIT License.