介绍
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。
通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器。
装饰器的写法:普通装饰器(无法传参) 、 装饰器工厂(可传参)。
装饰器是过去几年中 js 最大的成就之一,已是 Es7 的标准特性之一。
类装饰器
类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。
类装饰器:普通装饰器(无法传参)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function logClass(params: any) { console.log(params); 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();
|
类装饰器:装饰器工厂(可传参)
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); console.log(params); target.prototype.apiUrl = params; }; }
@logClass("http://www.baidu.com") class HttpClient { constructor() {} getData() {} }
var http: any = new HttpClient(); console.log(http.apiUrl);
|
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
下面是一个重载构造函数的例子。
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 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); console.log(attr); 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();
|
方法装饰器
方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。
方法装饰会在运行时传入下列 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); console.log(methodName); console.log(desc); 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.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");
|
参数装饰器
参数装饰器声明在一个参数声明之前(紧靠着参数声明)。 参数装饰器应用于类构造函数或方法声明。
参数装饰器表达式会在运行时当作函数被调用,传入下列 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); console.log(target); console.log(methodName); console.log(paramsIndex); 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); console.log(http.apiUrl);
|
装饰器执行顺序
先看测试代码:
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
|
从结果可以看出:
- 不同装饰器执行顺序:属性->方法->方法参数->类;
- 如果同一个类型的装饰器有多个,总是先执行后面的装饰器。