# 装饰器
ECMAScript 的装饰器提案到现在还没有定案 (opens new window),所以直接看 TS 中的装饰器。
同样在 TS 中,装饰器仍然是一项实验性特性,未来可能有所改变,所以如果你要使用装饰器,需要在 tsconfig.json 的编译配置中开启experimentalDecorators
,将它设为 true。
# 基本语法
# 定义
装饰器是一种新的声明,它能够作用于类声明、方法、访问符、属性和参数上。
使用@
符号加一个名字来定义,如@decorat
,这的 decorat 必须是一个函数或者求值后是一个函数。
这个 decorat 命名不是写死的,是你自己定义的;
这个函数在运行的时候被调用,被装饰的声明作为参数会自动传入。
要注意装饰器要紧挨着要修饰的内容的前面,而且所有的装饰器不能用在声明文件(.d.ts)中,和任何外部上下文中(比如 declare,关于.d.ts 和 declare,都会在讲声明文件部分学习)。
比如下面的这个函数,就可以作为装饰器使用:
function setProp (target) {
// ...
}
@setProp
先定义一个函数,然后这个函数有一个参数,就是要装饰的目标,装饰的作用不同,这个target代表的东西也不同。
定义了这个函数之后,它就可以作为装饰器,使用@函数名
的形式,写在要装饰的内容前面。
# 装饰器工厂
装饰器工厂也是一个函数,它的返回值是一个函数,返回的函数作为装饰器的调用函数。如果使用装饰器工厂,那么在使用的时候,就要加上函数调用,如下:
function setProp () {
return function (target) {
// ...
}
}
@setProp()
# 装饰器组合
装饰器可以组合,也就是对于同一个目标,引用多个装饰器:
// 可以写在一行
@setName @setAge target
// 可以换行
@setName
@setAge
target
但是这里要格外注意的是,多个装饰器的执行顺序:
- 装饰器工厂从上到下依次执行,但是只是用于返回函数但不调用函数;
- 装饰器函数从下到上依次执行,也就是执行工厂函数返回的函数。
以下面的两个装饰器工厂为例:
function setName () {
console.log('get setName')
return function (target) {
console.log('setName')
}
}
function setAge () {
console.log('get setAge')
return function (target) {
console.log('setAge')
}
}
@setName()
@setAge()
class Test {}
// 打印出来的内容如下:
/**
'get setName'
'get setAge'
'setAge'
'setName'
*/
可以看到,多个装饰器,会先执行装饰器工厂函数获取所有装饰器,然后再从后往前执行装饰器的逻辑。
# 装饰器执行顺序
类的定义中不同声明上的装饰器将按以下规定的顺序引用:
- 参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个实例成员;
- 参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个静态成员;
- 参数装饰器应用到构造函数;
- 类装饰器应用到类。