Typescript的装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。
通俗的讲,装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)。
对修饰器的实验支持是一项将在将来版本中更改的功能。
普通类装饰器定义
// 定义一个function,作为装饰器// 里面的params参数,其实就是调用该装饰器的类function logClass(params:any) {console.log(params);// 使用该装饰器的类,会增加一个apiUrl的属性params.prototype.apiUrl = 'xxxx';// 使用该装饰器的类,会增加一个run方法params.prototype.run = function(){console.log('run方法运行了.');}}// 使用装饰器@logClassclass HttpClient {constructor() {}getData() {}}// 测试let a:any = new HttpClient();console.log(a.apiUrl);a.run();
定义装饰器工厂
// 定义装饰器工厂,params即为调用时传入的参数,target即调用的类本身function logClass(params:string) {return function(target:any) {target.prototype.apiUri = params;}}// 此处必须要传入参数,否则会报错@logClass('http://xxxx')class HttpClient {constructor() {}getData() {}}let a:any = new HttpClient();console.log(a.apiUri);
类装饰器重写类的构造方法和其他方法
类装饰器表达式会在运行时当做函数被调用,类的构造函数作为唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
function logClass(target:any) {console.log(target);return class extends target {// 在类构造方法后运行,修改apiUrl的值apiUrl = '我是修改后的apiUrl';// 重写getData方法getData() {console.log('修改后的getData方法');console.log(this.apiUrl);}}}@logClassclass HttpClient {public apiUrl:string | undefined;constructor() {this.apiUrl = '构造方法中的apiUrl';}getData() {console.log(this.apiUrl);}}let client = new HttpClient();client.getData();
属性装饰器
属性装饰器表达式会在运行时当做函数被调用,传入下列2个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字
// 定义属性装饰器// params:传入的值// target:类的原型对象,即HttpClient.prototype// attr:属性名称function logProperty(params:any) {return function(target:any, attr:any) {console.log(params);console.log(target);console.log(attr);// 修改apiUrl属性的值target.attr = "https://yyyyyyy";}}class HttpClient {@logProperty("http://xxxxxxx")public apiUrl:string | undefined;constructor() {this.apiUrl = '构造方法中的apiUrl';}getData() {console.log(this.apiUrl);}}
方法装饰器
方法装饰器会在运行时传入下列三个参数:
1、对于静态成员来说,是类的构造函数;对于实例成员,是类的原型对象;
2、成员的名字;
3、成员的属性描述符;
// 定义方法装饰器// params: 传入的参数// target:如果方法是static的,target为类的构造函数;如果方法是实例化的,target为类的原型对象;// methodName:方法名称// describe:方法描述;describe.value就是方法本身function logMethod(params:any) {return function(target:any, methodName:any, describe:any) {console.log(params);console.log(target);console.log(methodName);console.log(describe);// 修改或扩展类的属性target.apiUrl = 'http://xxxxxx';// 修改或扩展类的方法target.run = function() {console.log('run 方法运行了.');}// 修改方法装饰器装饰的方法:将传入的参数转换为string类型let preMethod = describe.value; // 保存原来的方法// 替换掉原来的方法describe.value = function(...args:any[]) {console.log('我是修改后的describe方法');args = args.map((value:any) => String(value));console.log(args);// 调用原来的方法preMethod.apply(this, args);}}}class HttpClient {public apiUrl:string | undefined;constructor() {this.apiUrl = '构造方法中的apiUrl';}@logMethod("aaa")getData(...args:any[]) {// console.log(this.apiUrl);console.log(args); // 此处输出的args也都变成了字符串console.log('我是原来的getData方法');}}let client = new HttpClient();client.getData(1,2,'c');
方法参数装饰器
参数装饰器表达式会在运行时当做函数被调用,可以使用参数装饰器为类的原型增加一些数据元素,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象;
- 方法的名称;
- 参数在函数参数列表中的索引。
// 定义方法参数装饰器// params为调用该装饰器传入的参数// target:静态成员是类的构造函数,实例成员是类的原型对象;// methodName:方法名// index:属性的位置,从0开始function logParam(params:any) {return function(target:any, methodName:any, index:any) {console.log(params);console.log(target);console.log(methodName);console.log(index);}}class HttpClient {public apiUrl:string | undefined;constructor() {this.apiUrl = '构造方法中的apiUrl';}// 使用方法参数装饰器getData(@logParam('aaa') param:any) {}}let a = new HttpClient();a.getData('xxx');
装饰器执行顺序
从下向上、从内向外执行
// 参数加了问号变成可选参数,就可以使用@logClass()进行调用function logClass1(params?:any) {return function(target:any) {console.log('类装饰器1: '+ params);}}function logClass2(params?:any) {return function(target:any) {console.log('类装饰器2: '+ params);}}function logAttribute(params?:any) {return function(target:any, attributeName:any) {console.log('属性装饰器: '+ params;}}function logMethod(params?:any) {return function(target:any, methodName:any, desc:any) {console.log('方法装饰器: '+ params);}}function logParams(params?:any) {return function(target:any, methodName:any, index:any) {console.log('方法参数装饰器: '+ params);}}// 一个类上可以同时加上多个装饰器@logClass1('test1')@logClass1('test2')@logClass2('class2')class HttpClient {@logAttribute('apiUrl')public apiUrl:string | undefined;constructor() {this.apiUrl = '构造方法中的apiUrl';}@logMethod('getData')getData(@logParams('getDataParam') param:any) {}@logMethod('setData')setData(@logParams('setDataParam1') param1:any, @logParams('setDataParam2') param2:any) {}}let a = new HttpClient();a.getData('xxx');a.setData('xxxx','yyyyy');
运行结果:
属性装饰器: apiUrl方法参数装饰器: getDataParam方法装饰器: getData方法参数装饰器: setDataParam2方法参数装饰器: setDataParam1方法装饰器: setData类装饰器2: class2类装饰器1: test2类装饰器1: test1
