这篇主要记录TypeScript中的几种装饰器的概念与用法。装饰器(Decorators):用一种特性标注的写法作为声明,能够给类,方法,属性扩展功能,可以简单地理解为是非侵入式的行为修改。分为:类装饰器、方法装饰器、属性装饰器、访问器装饰器、参数装饰器。

类装饰器

1 /**
 2  * 装饰器(Decorators):用一种特性标注的写法作为声明,能够给类,方法,属性扩展功能,可以简单地理解为是非侵入式的行为修改
 3  * 分为:类装饰器、方法装饰器、属性装饰器、访问器装饰器、参数装饰器
 4  * 注:在tsconfig.json中将打开 “"experimentalDecorators": true” 以启用装饰器功能
 5  */
 6 
 7 /* ****************************类装饰器******************************* */
 8 //以下这个Person类使用了装饰器“yasuo”
 9 //@yasuo
10 class Person {
11     roleLine: string | undefined;
12 
13     constructor(readonly name: string, readonly birthDay: Date) {
14         this.roleLine = "Hello everyone"
15     }
16 
17     SayHi(): void {
18         console.log(this.roleLine);
19     }
20 }
21 
22 /**
23  * 装饰器其实也是一个方法,装饰器中可以做的事有三:
24  * (1)做点自己想做的事
25  * (2)扩展属性或者方法
26  * (3)重载类的构造函数
27  */
28 
29 //参数target便代表Person类(的构造器)
30 function yasuo(target: any) {
31     //(1)只要有类应用了这个装饰器,便会执行
32     console.log("Follow the wind, but watch your back.")
33 
34     //(2)为Person类扩展一个Attach的方法
35     target.prototype.Attack = function () {
36         console.log("Hasaki!");
37     }
38 
39     //(3)重载构造函数
40     return class extends target {
41         roleLine: any = "Make it quick."
42     }
43 }
44 //这里指定为any类型是为了防止调用扩展方法Attack时TypeScript编译器报错
45 let hero: any = new Person("yasuo", new Date("2013-12-14"));
46 
47 hero.SayHi();   // 控制台输出:Make it quick.
48 //hero.Attack()   // 控制台输出:Hasaki!
49 
50 
51 /* ****************************装饰器工厂****************************** */
52 // 带参数的装饰器
53 @skin("黑夜使者")
54 class Hero {
55     roleLine: string | undefined;
56 
57     constructor(readonly name: string, readonly birthDay: Date) {
58         this.roleLine = "welcome to LOL"
59     }
60 
61     SayHi(): void {
62         console.log(this.roleLine);
63     }
64 }
65 //定义“装饰器工厂”
66 function skin(value: string) {      // 这是一个装饰器工厂
67     return function (target: any) {      //  这是装饰器
68         let line: string;
69         switch (value) {
70             case "黑夜使者":
71                 line = "我是变革之风"
72                 break;
73             case "猩红之月":
74                 line = "Hasaki"
75                 break;
76             default:
77                 line = "Attack"
78                 break;
79         }
80 
81         target.prototype.Attack = function () {
82             console.log(line);
83         }
84     }
85 }
86 
87 let h1: any = new Hero("yasuo", new Date("2013-12-14"));
88 h1.Attack();    //控制台输出:我是变革之风

属性装饰器

1 /* ****************************属性装饰器****************************** */
 2 // 属性装饰器接收两个参数
 3 // 参数一:对于静态成员是构造器,对于实例成员是类的原型对象
 4 // 参数二:属性的名称
 5 
 6 class Hero {
 7 
 8     @lineInjection("Hasaki")    //使用装饰器为其赋值
 9     roleLine: string | undefined;
10 
11     constructor(readonly name: string, readonly birthDay: Date) {
12     }
13 
14     SayHi(): void {
15         console.log(this.roleLine);
16     }
17 }
18 
19 //定义一个属性装饰器
20 function lineInjection(line: string) {      //装饰器的参数
21     return function (target: any, prop: any) {
22         target[prop] = line;
23     }
24 }
25 
26 let h1 = new Hero("yasuo", new Date("2013-12-14"));
27 h1.SayHi();     //控制台输出:Hasaki

方法装饰器

1 /* ****************************方法装饰器****************************** */
 2 // 用于监视,修改方法,接受3个参数
 3 // 参数一:对于静态成员是构造器,对于实例成员是类的原型对象
 4 // 参数二:方法的名称
 5 // 参数三:方法的属性描述符,其中value属性就是当前方法的定义
 6 
 7 class Person {
 8     constructor(public firstName: string, public lastName: string) { }
 9 
10     @useFullName
11     public SayHi(): void {
12         console.log(`i am ${this.firstName}`);
13     }
14 }
15 
16 //定义一个方法装饰器,修改方法,让其输出全名
17 function useFullName(
18     target: object,         //参数一:类的原型对象
19     propertyName: string,   //参数二:成员的名称
20     descriptor: PropertyDescriptor  //参数三:成员的描述信息
21 ): void {
22 
23     //将改写前的原方法暂存下来
24     const sayLastName: Function = descriptor.value;
25 
26     //改写方法
27     descriptor.value = function () {
28         //先调用一次原方法
29         sayLastName.call(this);
30         //再输出一次全名
31         console.log(`${this.firstName} ${this.lastName}`)
32     };
33 }
34 
35 let mj = new Person("bond", "james");
36 
37 mj.SayHi();     //控制台输出:i am bond
38                 //bond james

方法参数装饰器

1 /* ****************************方法参数装饰器****************************** */
 2 // 可以用来监视方法传入的参数
 3 // 和之前几个装饰器类似,接受的参数如下
 4 // 参数一:对于静态成员是构造器,对于实例成员是类的原型对象
 5 // 参数二:方法的名称
 6 // 参数三:参数在方法中的位置(index)
 7 
 8 class Greeter {
 9     greeting: string;
10 
11     constructor(message: string) {
12         this.greeting = message;
13     }
14 
15     greet(@log name: string) {
16         return "Hello " + name + ", " + this.greeting;
17     }
18 }
19 
20 function log(
21     target: Object,
22     propertyKey: string,
23     parameterIndex: number) {
24 
25     console.log(target);
26     console.log(propertyKey);
27     console.log(parameterIndex);
28 }

装饰器的执行顺序

如果一个对象中包含多个装饰器,如

1 @f
2 @g
3 class x

那么他的执行顺序是f(g(x)),先执行下面的装饰器后再执行上面的。

若多种装饰器共存是,其执行顺序是:属性装饰器 → 方法装饰器 → 方法参数装饰器 → 类装饰器。