10Typescript中的装饰器

介绍

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。
通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器。
装饰器的写法:普通装饰器(无法传参) 、 装饰器工厂(可传参)。
装饰器是过去几年中 js 最大的成就之一,已是 Es7 的标准特性之一。

类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。

类装饰器:普通装饰器(无法传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function logClass(params: any) {
// params就是当前类
console.log(params); // HttpClient
params.prototype.apiUrl = "动态扩展的属性";
params.prototype.run = function () {
console.log("我是一个run方法");
};
}

@logClass
class HttpClient {
constructor() {}
getData() {}
}

var http: any = new HttpClient();
console.log(http.apiUrl); // 动态扩展的属性
http.run(); // 我是一个run方法

类装饰器:装饰器工厂(可传参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function logClass(params: string) {
return function (target: any) {
console.log(target); // target就是当前类HttpClient
console.log(params); // http://www.baidu.com
target.prototype.apiUrl = params;
};
}

@logClass("http://www.baidu.com")
class HttpClient {
constructor() {}
getData() {}
}

var http: any = new HttpClient();
console.log(http.apiUrl); // http://www.baidu.com

类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
下面是一个重载构造函数的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function logClass(target: any) {
console.log(target);
return class extends target {
apiUrl: any = "我是修改后的数据";
getData() {
this.apiUrl += "----";
console.log(this.apiUrl);
}
};
}

@logClass
class HttpClient {
public apiUrl: string | undefined;
constructor() {
this.apiUrl = "我是构造函数里面的apiUrl";
}
getData() {
console.log(this.apiUrl);
}
}

var http = new HttpClient();
console.log(http.apiUrl); // 我是修改后的数据
http.getData(); // 我是修改后的数据----

属性装饰器

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

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

我们可以用它来记录这个属性的元数据,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 属性装饰器
function logProperty(params: any) {
return function (target: any, attr: any) {
console.log(target); // 类的原型对象: {getData: ƒ, constructor: ƒ}
console.log(attr); // apiUrl
target[attr] = params;
};
}
class HttpClient {
@logProperty("http://www.baidu.com")
public apiUrl: string | undefined;
constructor() {}
getData() {
console.log(this.apiUrl);
}
}

var http = new HttpClient();
http.getData(); // "http://www.baidu.com"

方法装饰器

方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。

方法装饰会在运行时传入下列 3 个参数:

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

示例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function get(params: any) {
return function (target: any, methodName: any, desc: any) {
console.log(target); // 类的原型对象: {getData: ƒ, constructor: ƒ}
console.log(methodName); // getData
console.log(desc); // {writable: true, enumerable: true, configurable: true, value: ƒ}
target.apiUrl = params;
target.run = function () {
console.log("run");
};
};
}

class HttpClient {
public apiUrl: string | undefined;
constructor() {}

@get("http://www.baidu.com")
getData() {
console.log(this.apiUrl);
}
}

var http: any = new HttpClient();
http.getData(); // http://www.baidu.com
http.run(); // run

示例二:
我们的目标是修改装饰器的方法,把装饰器方法里面传入的所有参数改为 string 类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 方法装饰器
function get(params: any) {
return function (target: any, methodName: any, desc: any) {
let oMethod = desc.value;
desc.value = function (...args: any[]) {
args = args.map((value) => {
return String(params + "/" + value);
});
oMethod.call(this, ...args);
};
};
}

class HttpClient {
public apiUrl: string | undefined;
constructor() {}

@get("http://www.baidu.com")
getData(...args: any[]) {
console.log(args);
console.log("我是getData里面的方法");
}
}

var http: any = new HttpClient();
http.getData(123, "xxx"); // ['http://www.baidu.com/123', 'http://www.baidu.com/xxx']

参数装饰器

参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。

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

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 参数在函数参数列表中的索引。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 参数装饰器
function logParams(params: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log(params); // http://jd.com
console.log(target); // 类的原型对象: {getData: ƒ, constructor: ƒ}
console.log(methodName); // getData
console.log(paramsIndex); // 0
target.apiUrl = params;
};
}

class HttpClient {
public apiUrl: string | undefined;
constructor() {}

getData(@logParams("http://jd.com") uuid: any) {
console.log(uuid);
}
}

var http: any = new HttpClient();
http.getData(123456); // 123456
console.log(http.apiUrl); // http://jd.com

装饰器执行顺序

先看测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 类装饰器
function logClass1(params: string) {
return function (target: any) {
console.log("类装饰器1");
};
}
function logClass2(params: string) {
return function (target: any) {
console.log("类装饰器2");
};
}
// 属性装饰器
function logProperty1(params?: string) {
return function (target: any, attr: any) {
console.log("属性装饰器1");
};
}
function logProperty2(params?: string) {
return function (target: any, attr: any) {
console.log("属性装饰器2");
};
}

// 方法装饰器
function logMethod1(params?: any) {
return function (target: any, methodName: any, desc: any) {
console.log("方法装饰器1");
};
}
function logMethod2(params?: any) {
return function (target: any, methodName: any, desc: any) {
console.log("方法装饰器2");
};
}

// 方法参数装饰器
function logParams1(params?: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log("方法参数装饰器1");
};
}
function logParams2(params?: any) {
return function (target: any, methodName: any, paramsIndex: any) {
console.log("方法参数装饰器2");
};
}

@logClass1("http://www.baidu.com")
@logClass2("http://www.jd.com")
class HttpClient {
@logProperty1()
@logProperty2()
public apiUrl: string | undefined;
constructor() {}
@logMethod1()
@logMethod2()
getData() {}
setData(@logParams1() attr1: any, @logParams2() attr2: any) {}
}

var http: any = new HttpClient();

控制台打印结果:

1
2
3
4
5
6
7
8
属性装饰器2
属性装饰器1
方法装饰器2
方法装饰器1
方法参数装饰器2
方法参数装饰器1
类装饰器2
类装饰器1

从结果可以看出:

  1. 不同装饰器执行顺序:属性->方法->方法参数->类;
  2. 如果同一个类型的装饰器有多个,总是先执行后面的装饰器。