随着TypeScript及ES6中类的引入,就有了一些需要对类与类的成员进行批注与修改等额外特性的场景(With the introduction of Classes in TypeScript and ES6, there now exist certain scennarios that require additional features to support annotating or modifying classes and class members)。装饰器这一特性,就提供了一种将类声明与成员的批注与元编程语法加入进来的方式。装饰器特性,是一项JavaScript的[第二阶段提议](https://github.com/tc39/proposal-decorators),且是TypeScript的一项实验特性。
*装饰器* 是一类特殊的声明,可被附加到[类的声明](#class-decorators)、[方法](#method-decorators)、[访问器](#accessor-decorators)、[属性](#property-decorators)或者[参数](#parameter-decorators)。装饰器采用`@expression`形式,其中的`expression`求值后必须是一个函数,在运行时该函数将以被装饰的声明有关的信息,被调用到(Decorators use the form `@expression`, where `expression` must evaluate to a function that will be called at runtime with information about the decorated declaration)。
可通过编写一个装饰器工厂,来对装饰器作用于声明的方式进行定制。 *装饰器工厂* 就是一个返回由装饰器在运行时调用的表达式的函数(If you want to customize how a decorator is applied to a declaration, we can write a decorator factory. A *Decorator Factory* is simply a function that returns the expression that will be called by the decorator at runtime)。
1. 对于各个实例成员, *参数装饰器*,接着分别是 *方法*、*访问器* 或者 *属性装饰器* 将被应用( *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each instance member)。
2. 对于各个静态成员, *参数装饰器*,接着分别是 *方法*、*访问器* 或者 *属性装饰器* 将被应用( *Parameter Decorators*, followed by *Method*, *Accessor*, or *Property Decorators* are applied for each static member)。
3. 对于构造器,将应用参数装饰器( *Parameter Decorators* are applied for the constructor)。
4. 对于类,将应用 *类装饰器*(*Class Decorators* are applied for the class )。
*类装饰器* 是在类声明之前、紧接着类声明处使用的。类声明作用与类的构造器,而可用于对类的定义进行观察、修改或替换。类装饰器不能在声明文件,或任何其它外围上下文中使用(比如在某个`declare`类上。The class decorator is applied to the constructor of the class and can be used to observe, modify or replace a class definition. A class decorator cannot be used in a declaration file, or in any other ambient context(such as on a `declare` class))。
> **注意** 应注意返回一个新的构造器函数,因为必须注意维护好原来的原型。运行时对装饰器的应用这一逻辑,并不会做这件事(Should you chose to return a new constructor function, you must take care to maintain the original prototype. The logic that applies decorators at runtime will not do this for you)。
下面是一个应用到`Greeter`类的类装饰器(`@sealed`)的示例:
```typescript
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greeter () {
return `Hello, { this.greeting }`;
}
}
```
可将`@sealed`装饰器定义为使用下面的函数声明:
```typescript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
```
在`@sealed`装饰器(于运行时)被执行后,它将同时封闭构造器及其原型。
接着的是一个如何覆写构造器的示例:
```typescript
function classDecorator<Textends{new(...args:any[]):{}}>(constructor: T) {
*方法装饰器* 是在某个方法声明之前、紧接着该方法处使用的。此种装饰器被应用到方法的 *属性描述符* ,而可用于对方法定义进行观察、修改或替换。不能在定义文件、过载或任何其它已有上下文中(比如某个`declare`类中)使用方法装饰器(The decorator is applied to the *Property Descriptor* for the method, and can be used to observe, modify, or replace a method definition. A method decorator cannot be used in a declaration file, on an overload, or in any other ambient context(such as in a `declare` class))。
方法装饰器的表达式,将在运行时作为函数,以下面的三个参数进行调用:
1. 静态成员的类构造函数,或实例成员的类的原型(Either the constructor function of the class for a static member, or the prototype of the class for an instance member, )。
> **注意** 对与单个成员,TypeScript是不允许对其`get`或`set`访问器进行装饰的。而是该成员的所有装饰器,都必须应用到按文档顺序所指定的第一个访问器(TypeScript disallows decorating both the `get` and `set` accessor for a single member. Instead, all decorators for the member must be applied to the first accessor specified in document order)。这是因为应用到 *熟悉描述符* 的那些结合了`get`与`set`访问器的装饰器,并不是各自单独声明的。
*参数装饰器*,是紧接着某个参数声明之前进行声明的。参数装饰器应用到类构造器或类的方法声明的函数上的(the parameter decorator is applied to the function for a class constructor or method declaration)。参数装饰器不能用在声明文件(`.d.ts`)、重载(overload)或其他外围上下文(ambient context,比如在某个`declare`类中)。
参数装饰器的表达式在运行时将作为函数加以调用,其有着以下三个参数:
1. 静态成员的类的构造函数,或实例成员的类的原型;
2. 成员的名称;
3. 对应参数在函数参数列表中的顺序索引。
> **注意** 参数装饰器只能用于对某个方法上已声明的某个参数进行观察(A parameter decorator can only be used to observe that a parameter has been declared on a method)。
这里的`@required`装饰器加入了一个将该参数标识为必需的元数据条目(the `@required` decorator adds a metadata entry that marks the parameter as required)。而`@validate`装饰器随后将现有的`greet`方法,封装为一个在调用该原始方法前,对参数进行验证的函数。
TypeScript包含了对那些有着装饰器的声明的确定类型的元数据的生成的实验性支持(TypeScript includes experimental support for emitting certain types of metadata for declarations that have decorators)。要开启此项实验性支持功能,就必须通过命令行或在`tsconfig.json`文件中对`emitDecoratorMetadata`编译器选项进行设置: