装饰器

[toc]

学习目标

  • 了解装饰器语法,学会使用装饰器对类进行扩展
  • 清楚装饰器执行顺序
  • 了解元数据以及针对装饰器的元数据编程

什么是装饰器

装饰器-DecoratorsTypeScript 中是一种可以在不修改类代码的基础上通过添加标注的方式来对类型进行扩展的一种方式

  • 减少代码量
  • 提高代码扩展性、可读性和维护性

TypeScript 中,装饰器只能在类中使用

装饰器语法

装饰器的使用极其的简单

  • 装饰器本质就是一个函数
  • 通过特定语法在特定的位置调用装饰器函数即可对数据(类、方法、甚至参数等)进行扩展

启用装饰器特性

  • experimentalDecorators: true
  1. // 装饰器函数
  2. function log(target: Function, type: string, descriptor: PropertyDescriptor) {
  3. let value = descriptor.value;
  4. descriptor.value = function(a: number, b: number) {
  5. let result = value(a, b);
  6. console.log('日志:', {
  7. type,
  8. a,
  9. b,
  10. result
  11. })
  12. return result;
  13. }
  14. }
  15. // 原始类
  16. class M {
  17. @log
  18. static add(a: number, b: number) {
  19. return a + b;
  20. }
  21. @log
  22. static sub(a: number, b: number) {
  23. return a - b;
  24. }
  25. }
  26. let v1 = M.add(1, 2);
  27. console.log(v1);
  28. let v2 = M.sub(1, 2);
  29. console.log(v2);

装饰器

装饰器 是一个函数,它可以通过 @装饰器函数 这种特殊的语法附加在 方法访问符属性参数 上,对它们进行包装,然后返回一个包装后的目标对象(方法访问符属性参数 ),装饰器工作在类的构建阶段,而不是使用阶段

  1. function 装饰器1() {}
  2. ...
  3. @装饰器1
  4. class MyClass {
  5. @装饰器2
  6. a: number;
  7. @装饰器3
  8. static property1: number;
  9. @装饰器4
  10. get b() {
  11. return 1;
  12. }
  13. @装饰器5
  14. static get c() {
  15. return 2;
  16. }
  17. @装饰器6
  18. public method1(@装饰器5 x: number) {
  19. //
  20. }
  21. @装饰器7
  22. public static method2() {}
  23. }

类装饰器

目标

  • 应用于类的构造函数

参数

  • 第一个参数(也只有一个参数)

    • 类的构造函数作为其唯一的参数

方法装饰器

目标

  • 应用于类的方法上

参数

  • 第一个参数

    • 静态方法:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数

    • 方法名称
  • 第三个参数

    • 方法描述符对象

属性装饰器

目标

  • 应用于类的属性上

参数

  • 第一个参数

    • 静态方法:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数

    • 属性名称

访问器装饰器

目标

  • 应用于类的访问器(getter、setter)上

参数

  • 第一个参数

    • 静态方法:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数

    • 属性名称
  • 第三个参数

    • 方法描述符对象

参数装饰器

目标

  • 应用在参数上

参数

  • 第一个参数

    • 静态方法:类的构造函数
    • 实例方法:类的原型对象
  • 第二个参数

    • 方法名称
  • 第三个参数

    • 参数在函数参数列表中的索引

装饰器执行顺序

实例装饰器

  1. 属性 => 访问符 => 参数 => 方法

静态装饰器

  1. 属性 => 访问符 => 参数 => 方法

装饰器工厂

如果我们需要给装饰器执行过程中传入一些参数的时候,就可以使用装饰器工厂来实现

  1. // 装饰器函数
  2. function log(callback: Function) {
  3. return function(target: Function, type: string, descriptor: PropertyDescriptor) {
  4. let value = descriptor.value;
  5. descriptor.value = function(a: number, b: number) {
  6. let result = value(a, b);
  7. callback({
  8. type,
  9. a,
  10. b,
  11. result
  12. });
  13. return result;
  14. }
  15. }
  16. }
  17. // 原始类
  18. class M {
  19. @log(function(result: any) {
  20. console.log('日志:', result)
  21. })
  22. static add(a: number, b: number) {
  23. return a + b;
  24. }
  25. @log(function(result: any) {
  26. localStorage.setItem('log', JSON.stringify(result));
  27. })
  28. static sub(a: number, b: number) {
  29. return a - b;
  30. }
  31. }
  32. let v1 = M.add(1, 2);
  33. console.log(v1);
  34. let v2 = M.sub(1, 2);
  35. console.log(v2);

元数据

装饰器 函数中 ,我们可以拿到 方法访问符属性参数 的基本信息,如它们的名称,描述符 等,但是我们想获取更多信息就需要通过另外的方式来进行:元数据

什么是元数据?

元数据 :用来描述数据的数据,在我们的程序中,对象 等都是数据,它们描述了某种数据,另外还有一种数据,它可以用来描述 对象,这些用来描述数据的数据就是 元数据

比如一首歌曲本身就是一组数据,同时还有一组用来描述歌曲的歌手、格式、时长的数据,那么这组数据就是歌曲数据的元数据

使用 reflect-metadata

https://www.npmjs.com/package/reflect-metadata

首先,需要安装 reflect-metadata

  1. npm install reflect-metadata

定义元数据

我们可以 方法 等数据定义元数据

  • 元数据会被附加到指定的 方法 等数据之上,但是又不会影响 方法 本身的代码

设置

Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)

  • metadataKey:meta 数据的 key
  • metadataValue:meta 数据的 值
  • target:meta 数据附加的目标
  • propertyKey:对应的 property key

调用方式

  • 通过 Reflect.defineMetadata 方法调用来添加 元数据

  • 通过 @Reflect.metadata 装饰器来添加 元数据

  1. import "reflect-metadata"
  2. @Reflect.metadata("n", 1)
  3. class A {
  4. @Reflect.metadata("n", 2)
  5. public static method1() {
  6. }
  7. @Reflect.metadata("n", 4)
  8. public method2() {
  9. }
  10. }
  11. // or
  12. Reflect.defineMetadata('n', 1, A);
  13. Reflect.defineMetadata('n', 2, A, 'method1');
  14. let obj = new A();
  15. Reflect.defineMetadata('n', 3, obj);
  16. Reflect.defineMetadata('n', 4, obj, 'method2');
  17. console.log(Reflect.getMetadata('n', A));
  18. console.log(Reflect.getMetadata('n', A, ));

获取

Reflect.getMetadata(metadataKey, target, propertyKey)

参数的含义与 defineMetadata 对应

使用元数据的 log 装饰器

  1. import "reflect-metadata"
  2. function L(type = 'log') {
  3. return function(target: any) {
  4. Reflect.defineMetadata("type", type, target);
  5. }
  6. }
  7. // 装饰器函数
  8. function log(callback: Function) {
  9. return function(target: any, name: string, descriptor: PropertyDescriptor) {
  10. let value = descriptor.value;
  11. let type = Reflect.getMetadata("type", target);
  12. descriptor.value = function(a: number, b: number) {
  13. let result = value(a, b);
  14. if (type === 'log') {
  15. console.log('日志:', {
  16. name,
  17. a,
  18. b,
  19. result
  20. })
  21. }
  22. if (type === 'storage') {
  23. localStorage.setItem('storageLog', JSON.stringify({
  24. name,
  25. a,
  26. b,
  27. result
  28. }));
  29. }
  30. return result;
  31. }
  32. }
  33. }
  34. // 原始类
  35. @L('log')
  36. class M {
  37. @log
  38. static add(a: number, b: number) {
  39. return a + b;
  40. }
  41. @log
  42. static sub(a: number, b: number) {
  43. return a - b;
  44. }
  45. }
  46. let v1 = M.add(1, 2);
  47. console.log(v1);
  48. let v2 = M.sub(1, 2);
  49. console.log(v2);

使用 emitDecoratorMetadata

tsconfig.json 中有一个配置 emitDecoratorMetadata,开启该特性,typescript 会在编译之后自动给 方法访问符属性参数 添加如下几个元数据

  • design:type:被装饰目标的类型

    • 成员属性:属性的标注类型
    • 成员方法:Function 类型
  • design:paramtypes

    • 成员方法:方法形参列表的标注类型
    • 类:构造函数形参列表的标注类型
  • design:returntype

    • 成员方法:函数返回值的标注类型
  1. import "reflect-metadata"
  2. function n(target: any) {
  3. }
  4. function f(name: string) {
  5. return function(target: any, propertyKey: string, descriptor: any) {
  6. console.log( 'design type', Reflect.getMetadata('design:type', target, propertyKey) );
  7. console.log( 'params type', Reflect.getMetadata('design:paramtypes', target, propertyKey) );
  8. console.log( 'return type', Reflect.getMetadata('design:returntype', target, propertyKey) );
  9. }
  10. }
  11. function m(target: any, propertyKey: string) {
  12. }
  13. @n
  14. class B {
  15. @m
  16. name: string;
  17. constructor(a: string) {
  18. }
  19. @f('')
  20. method1(a: string, b: string) {
  21. return 'a'
  22. }
  23. }

编译后

  1. __decorate([
  2. m,
  3. __metadata("design:type", String)
  4. ], B.prototype, "name", void 0);
  5. __decorate([
  6. f(''),
  7. __metadata("design:type", Function),
  8. __metadata("design:paramtypes", [String, String]),
  9. __metadata("design:returntype", void 0)
  10. ], B.prototype, "method1", null);
  11. B = __decorate([
  12. n,
  13. __metadata("design:paramtypes", [String])
  14. ], B);