# 装饰器

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'
*/

可以看到,多个装饰器,会先执行装饰器工厂函数获取所有装饰器,然后再从后往前执行装饰器的逻辑。

# 装饰器执行顺序

类的定义中不同声明上的装饰器将按以下规定的顺序引用:

  1. 参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个实例成员;
  2. 参数装饰器,方法装饰器,访问符装饰器或属性装饰器应用到每个静态成员;
  3. 参数装饰器应用到构造函数;
  4. 类装饰器应用到类。