装饰器
2023-10-07 11:20:15
定义
装饰器是一种语法结构,用来在定义类时修改类的一些行为
在语法上,装饰器有如下几个特征
- 第一个字符(或者说前缀)是@,后面是一个表达式
- @后面的表达式,必须是一个函数(或者执行后可以得到一个函数)
- 这个函数接受所修饰对象的一些相关值作为参数
- 这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象
function decorator(value, context) {
console.log(`hello this is ${context.kind} ${context.name}`)
}
@decorator
class Person {} // "hello this is class Person"
2
3
4
5
6
如上代码,类 Person 在执行前会先执行装饰器 decorator,并且会向装饰器自动传入参数
装饰器的结构
装饰器的函数定义类型
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
2
3
4
5
6
7
8
9
10
11
12
13
14
上面代码中,Decorator 是装饰器的类型定义。它是一个函数,使用时会接收到 value 和 context 两个参数。
- value:所装饰的对象。
- context:上下文对象,TypeScript 提供一个原生接口 ClassMethodDecoratorContext,描述这个对象。
所以定义装饰器的时候类型可以写成这样
function decorator(value: any, context: ClassMethodDecoratorContext) {
console.log(`hello this is ${context.kind} ${context.name}`)
}
2
3
context 对象的属性会根据所装饰对象的不同而不同,其中有两个属性(kind 和 name)是必有的
kind:字符串,表示所装饰对象的类型,一共有六种类型的装饰器
- class
- method
- getter
- setter
- field
- accessor
name:字符串或者 Symbol 值,所装饰对象的名字,比如类名、属性名等
addInitializer():函数,用来添加类的初始化逻辑。之前这些逻辑通常放在构造函数里面,对方法进行初始化,现在改成以函数形式传入 addInitializer()方法。注意,addInitializer()没有返回值。
private:布尔值,表示所装饰的对象是否为类的私有成员
static:布尔值,表示所装饰的对象是否为类的静态成员
access:一个对象,包含了某个值的 get 和 set 方法
使用
这里类装饰器返回一个函数,替代当前类的构造方法
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上面代码中,类装饰器 @countInstances 返回一个函数,替换了类 MyClass 的构造方法。新的构造方法实现了实例的计数,每新建一个实例,计数器就会加一,并且对实例添加 count 属性,用来表示当前实例的编号。
注意,为了确保新构造方法继承定义在 MyClass 的原型之上的成员,加入第11行的代码,确保两者的原型对象是一致的。否则,新的构造函数 wrapper 的原型对象,与 MyClass 不同,无法通过 instanceof 运算符。