Typescript的装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、属性或参数上,可以修改类的行为。

通俗的讲,装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。

常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器

装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)。

对修饰器的实验支持是一项将在将来版本中更改的功能。

普通类装饰器定义

  1. // 定义一个function,作为装饰器
  2. // 里面的params参数,其实就是调用该装饰器的类
  3. function logClass(params:any) {
  4. console.log(params);
  5. // 使用该装饰器的类,会增加一个apiUrl的属性
  6. params.prototype.apiUrl = 'xxxx';
  7. // 使用该装饰器的类,会增加一个run方法
  8. params.prototype.run = function(){
  9. console.log('run方法运行了.');
  10. }
  11. }
  12. // 使用装饰器
  13. @logClass
  14. class HttpClient {
  15. constructor() {
  16. }
  17. getData() {
  18. }
  19. }
  20. // 测试
  21. let a:any = new HttpClient();
  22. console.log(a.apiUrl);
  23. a.run();

定义装饰器工厂

  1. // 定义装饰器工厂,params即为调用时传入的参数,target即调用的类本身
  2. function logClass(params:string) {
  3. return function(target:any) {
  4. target.prototype.apiUri = params;
  5. }
  6. }
  7. // 此处必须要传入参数,否则会报错
  8. @logClass('http://xxxx')
  9. class HttpClient {
  10. constructor() {
  11. }
  12. getData() {
  13. }
  14. }
  15. let a:any = new HttpClient();
  16. console.log(a.apiUri);

类装饰器重写类的构造方法和其他方法

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

如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

  1. function logClass(target:any) {
  2. console.log(target);
  3. return class extends target {
  4. // 在类构造方法后运行,修改apiUrl的值
  5. apiUrl = '我是修改后的apiUrl';
  6. // 重写getData方法
  7. getData() {
  8. console.log('修改后的getData方法');
  9. console.log(this.apiUrl);
  10. }
  11. }
  12. }
  13. @logClass
  14. class HttpClient {
  15. public apiUrl:string | undefined;
  16. constructor() {
  17. this.apiUrl = '构造方法中的apiUrl';
  18. }
  19. getData() {
  20. console.log(this.apiUrl);
  21. }
  22. }
  23. let client = new HttpClient();
  24. client.getData();

属性装饰器

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

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

2、成员的名字

  1. // 定义属性装饰器
  2. // params:传入的值
  3. // target:类的原型对象,即HttpClient.prototype
  4. // attr:属性名称
  5. function logProperty(params:any) {
  6. return function(target:any, attr:any) {
  7. console.log(params);
  8. console.log(target);
  9. console.log(attr);
  10. // 修改apiUrl属性的值
  11. target.attr = "https://yyyyyyy";
  12. }
  13. }
  14. class HttpClient {
  15. @logProperty("http://xxxxxxx")
  16. public apiUrl:string | undefined;
  17. constructor() {
  18. this.apiUrl = '构造方法中的apiUrl';
  19. }
  20. getData() {
  21. console.log(this.apiUrl);
  22. }
  23. }

方法装饰器

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

1、对于静态成员来说,是类的构造函数;对于实例成员,是类的原型对象;

2、成员的名字;

3、成员的属性描述符;

  1. // 定义方法装饰器
  2. // params: 传入的参数
  3. // target:如果方法是static的,target为类的构造函数;如果方法是实例化的,target为类的原型对象;
  4. // methodName:方法名称
  5. // describe:方法描述;describe.value就是方法本身
  6. function logMethod(params:any) {
  7. return function(target:any, methodName:any, describe:any) {
  8. console.log(params);
  9. console.log(target);
  10. console.log(methodName);
  11. console.log(describe);
  12. // 修改或扩展类的属性
  13. target.apiUrl = 'http://xxxxxx';
  14. // 修改或扩展类的方法
  15. target.run = function() {
  16. console.log('run 方法运行了.');
  17. }
  18. // 修改方法装饰器装饰的方法:将传入的参数转换为string类型
  19. let preMethod = describe.value; // 保存原来的方法
  20. // 替换掉原来的方法
  21. describe.value = function(...args:any[]) {
  22. console.log('我是修改后的describe方法');
  23. args = args.map((value:any) => String(value));
  24. console.log(args);
  25. // 调用原来的方法
  26. preMethod.apply(this, args);
  27. }
  28. }
  29. }
  30. class HttpClient {
  31. public apiUrl:string | undefined;
  32. constructor() {
  33. this.apiUrl = '构造方法中的apiUrl';
  34. }
  35. @logMethod("aaa")
  36. getData(...args:any[]) {
  37. // console.log(this.apiUrl);
  38. console.log(args); // 此处输出的args也都变成了字符串
  39. console.log('我是原来的getData方法');
  40. }
  41. }
  42. let client = new HttpClient();
  43. client.getData(1,2,'c');

方法参数装饰器

参数装饰器表达式会在运行时当做函数被调用,可以使用参数装饰器为类的原型增加一些数据元素,传入下列3个参数:

  1. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象;
  2. 方法的名称;
  3. 参数在函数参数列表中的索引。
  1. // 定义方法参数装饰器
  2. // params为调用该装饰器传入的参数
  3. // target:静态成员是类的构造函数,实例成员是类的原型对象;
  4. // methodName:方法名
  5. // index:属性的位置,从0开始
  6. function logParam(params:any) {
  7. return function(target:any, methodName:any, index:any) {
  8. console.log(params);
  9. console.log(target);
  10. console.log(methodName);
  11. console.log(index);
  12. }
  13. }
  14. class HttpClient {
  15. public apiUrl:string | undefined;
  16. constructor() {
  17. this.apiUrl = '构造方法中的apiUrl';
  18. }
  19. // 使用方法参数装饰器
  20. getData(@logParam('aaa') param:any) {
  21. }
  22. }
  23. let a = new HttpClient();
  24. a.getData('xxx');

装饰器执行顺序

从下向上、从内向外执行

  1. // 参数加了问号变成可选参数,就可以使用@logClass()进行调用
  2. function logClass1(params?:any) {
  3. return function(target:any) {
  4. console.log('类装饰器1: '+ params);
  5. }
  6. }
  7. function logClass2(params?:any) {
  8. return function(target:any) {
  9. console.log('类装饰器2: '+ params);
  10. }
  11. }
  12. function logAttribute(params?:any) {
  13. return function(target:any, attributeName:any) {
  14. console.log('属性装饰器: '+ params;
  15. }
  16. }
  17. function logMethod(params?:any) {
  18. return function(target:any, methodName:any, desc:any) {
  19. console.log('方法装饰器: '+ params);
  20. }
  21. }
  22. function logParams(params?:any) {
  23. return function(target:any, methodName:any, index:any) {
  24. console.log('方法参数装饰器: '+ params);
  25. }
  26. }
  27. // 一个类上可以同时加上多个装饰器
  28. @logClass1('test1')
  29. @logClass1('test2')
  30. @logClass2('class2')
  31. class HttpClient {
  32. @logAttribute('apiUrl')
  33. public apiUrl:string | undefined;
  34. constructor() {
  35. this.apiUrl = '构造方法中的apiUrl';
  36. }
  37. @logMethod('getData')
  38. getData(@logParams('getDataParam') param:any) {
  39. }
  40. @logMethod('setData')
  41. setData(@logParams('setDataParam1') param1:any, @logParams('setDataParam2') param2:any) {
  42. }
  43. }
  44. let a = new HttpClient();
  45. a.getData('xxx');
  46. a.setData('xxxx','yyyyy');

运行结果:

  1. 属性装饰器: apiUrl
  2. 方法参数装饰器: getDataParam
  3. 方法装饰器: getData
  4. 方法参数装饰器: setDataParam2
  5. 方法参数装饰器: setDataParam1
  6. 方法装饰器: setData
  7. 类装饰器2: class2
  8. 类装饰器1: test2
  9. 类装饰器1: test1