TypeScript学习笔记:装饰器(Decorators)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript学习笔记:装饰器(Decorators)相关的知识,希望对你有一定的参考价值。

装饰器简介

装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 

需要注意的是:装饰器是一项实验性特性,在未来的版本中可能会发生改变。

若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:

1 {
2     "compilerOptions": {
3         "target": "ES5",
4         "experimentalDecorators": true
5     }
6 }

如何定义

装饰器使用@expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

装饰器组合

多个装饰器可以同时应用到一个声明上,就像下面的示例:

书写在同一行上:

@f @g x

书写在多行上:

@f
@g
x

在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:

  1. 由上至下依次对装饰器表达式求值。
  2. 求值的结果会被当作函数,由下至上依次调用。

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中( .d.ts),也不能用在任何外部上下文中(比如declare的类)。

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。

我们来看一个例子:

 1 function sealed(constructor: Function) {
 2     Object.seal(constructor);
 3     Object.seal(constructor.prototype);
 4 }
 5 
 6 @sealed
 7 class MyClass {
 8     a: number = 0;
 9     b: string = "hello";
10 }
11 
12 var obj = new MyClass();
13 // obj.c = true; // 编译报错

通过类装饰器我们可以对类的原型对象做一定的修改。

方法装饰器

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。

方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符。

注意:如果代码输出目标版本小于ES5,属性描述符将会是undefined。

我们来看下面的例子:

 1 function methodDecorator(param1: boolean, param2?: string) {
 2     return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 3         console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor));
 4     };
 5 }
 6 
 7 class MyClass {
 8     @methodDecorator(true, "this is static")
 9     public static sFunc(): void {
10         console.log("call static method");
11     }
12 
13     @methodDecorator(false)
14     public func(): void {
15         console.log("call method");
16     }
17 }
18 
19 MyClass.sFunc();
20 MyClass.sFunc();
21 
22 var obj = new MyClass();
23 obj.func();
24 obj.func();

输出如下:

1 false, undefined, [object Object], func, {"writable":true,"enumerable":true,"configurable":true}
2 true, this is static, function MyClass() {
3     }, sFunc, {"writable":true,"enumerable":true,"configurable":true}
4 call static method
5 call static method
6 call method
7 call method

我们可以发现,方法装饰器返回的函数会在解释类的对应方法时被调用一次,并可以得到装饰器的参数和被装饰的方法的相关信息。

装饰器方法的调用只会在加载代码时执行一次,调用被装饰的方法不会触发装饰器方法。

访问器装饰器

访问器装饰器声明在一个访问器的声明之前(紧靠着访问器声明)。 访问器装饰器应用于访问器的 属性描述符并且可以用来监视,修改或替换一个访问器的定义。 访问器装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。

访问器装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 成员的属性描述符。

注意:如果代码输出目标版本小于ES5,Property Descriptor将会是undefined。

同时TypeScript不允许同时装饰一个成员的get和set访问器。取而代之的是,一个成员的所有装饰的必须应用在文档顺序的第一个访问器上。这是因为,在装饰器应用于一个属性描述符时,它联合了get和set访问器,而不是分开声明的。

我们来看一个例子:

 1 function methodDecorator(param1: boolean, param2?: string) {
 2     return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 3         console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey + ", " + JSON.stringify(descriptor));
 4     };
 5 }
 6 
 7 class MyClass {
 8     private static _myName: string;
 9 
10     @methodDecorator(true, "this is static")
11     public static set myName(value: string) {
12         this._myName = value;
13     }
14 
15     public static get myName(): string {
16         return this._myName;
17     }
18 
19     private _age: number;
20 
21     @methodDecorator(false)
22     public set age(value: number) {
23         this._age = value;
24     }
25 
26     public get age(): number {
27         return this._age;
28     }
29 }
30 
31 MyClass.myName = "hello";
32 console.log(MyClass.myName);
33 
34 var obj = new MyClass();
35 obj.age = 28;
36 console.log(obj.age);

输出如下:

1 false, undefined, [object Object], age, {"enumerable":true,"configurable":true}
2 true, this is static, function MyClass() {
3     }, myName, {"enumerable":true,"configurable":true}
4 hello
5 28

我们可以发现,访问器装饰器返回的函数会在解释类的对应访问器时被调用一次,并可以得到装饰器的参数和被装饰的访问器的相关信息。

装饰器方法的调用只会在加载代码时执行一次,调用被装饰的访问器不会触发装饰器方法。

属性装饰器

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如 declare的类)里。

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。

我们来看一个例子:

 1 function propDecorator(param1: boolean, param2?: string) {
 2     return function (target: any, propertyKey: string) {
 3         console.log(param1 + ", " + param2 + ", " + target + ", " + propertyKey);
 4     };
 5 }
 6 
 7 class MyClass {
 8     @propDecorator(false, "Hi")
 9     public static A: number = 0;
10 
11     @propDecorator(true)
12     public a: string = "hello";
13 }
14 
15 console.log(MyClass.A);
16 var obj = new MyClass();
17 console.log(obj.a);

输出如下:

1 true, undefined, [object Object], a
2 false, Hi, function MyClass() {
3         this.a = "hello";
4     }, A
5 0
6 hello

我们可以发现,属性装饰器返回的函数会在解释类的对应属性时被调用一次,并可以得到装饰器的参数和被装饰的属性的相关信息。

装饰器方法的调用只会在加载代码时执行一次,调用被装饰的属性不会触发装饰器方法。

参数装饰器

参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。 参数装饰器不能用在声明文件(.d.ts),重载或其它外部上下文(比如 declare的类)里。

参数装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 参数在函数参数列表中的索引。

我们来看一个例子:

 1 function paramDecorator(target: Object, propertyKey: string | symbol, parameterIndex: number) {
 2     console.log(target + ", " + <any> propertyKey + ", " + parameterIndex);
 3 }
 4 
 5 class MyClass {
 6     public func(@paramDecorator a: number, @paramDecorator b: string = "hello", @paramDecorator c?: boolean): void {
 7         console.log("call method");
 8     }
 9 }
10 
11 var obj = new MyClass();
12 obj.func(1);
13 obj.func(2);

输出如下:

1 [object Object], func, 2
2 [object Object], func, 1
3 [object Object], func, 0
4 call method
5 call method

我们可以发现,参数装饰器返回的函数会在解释方法的参数时被调用一次,并可以得到参数的相关信息。我们有3个参数就调用了3次。

装饰器方法的调用只会在加载代码时执行一次,调用被装饰的参数的方法不会触发装饰器方法。

以上是关于TypeScript学习笔记:装饰器(Decorators)的主要内容,如果未能解决你的问题,请参考以下文章

用于制作 python 装饰器类的精益接口

TypeScript深入学习TypeScript装饰器

细说Typescript中的装饰器

Python学习笔记012——装饰器

python学习笔记之装饰器(语法糖)

安全牛学习笔记python装饰器