前言

2015年,微软Typescript团队和谷歌的Angular团队合作,其中Typescript提供了装饰器的特性
2016年,装饰器作为JS的新的特性提案被Yehuda Katz提出。

至今该特性还处于Stage 2的阶段。因此在JS中是不可以直接使用装饰器特性的,本文主要讨论在typescript中的装饰器。

装饰器是一种特殊的声明方式,它可以附属于类、属性、方法、变量、accessor。 在代码运行装饰器的时候,会带入被装饰的声明的信息。

在Typescript中,装饰器特性现在还是一个是实验性特性

如何定义装饰器

从实现的角度说,一个装饰器就是一个普通的方法,只是不同类型的装饰器其入参值不同,而在方法的内部,可以自由实现自己想要的逻辑。

接下来看看不同的装饰器具体是如何定义的?

类装饰器

  1. /**
  2. * 定义一个类装饰器
  3. * @param {Function} constructor 被装饰的类的构造器
  4. */
  5. function classDecorator(constructor: Function) {
  6. // 对被装饰的类做各种操作
  7. }
  8. // -- 使用类装饰器 -- //
  9. @classDecorator
  10. class ClassToBeDecorated {}

方法装饰器

  1. /**
  2. * 定义一个方法装饰器
  3. *
  4. * @param {Function} target 如果装饰是静态方法,则为类的构造器(constructor); 如果被装饰的是实例方法,则为类的原型
  5. * @param {string} propertyKey
  6. * @param {PropertyDescriptor} descriptor 被装饰的方法的描述符
  7. */
  8. function methodDecorator(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  9. }
  10. // -- 使用方法装饰器 -- //
  11. class ClassToBeDecorated {
  12. @methodDecorator
  13. methodToBeDecorated(){}
  14. @methodDecorator
  15. static staticMethodToBeDecorated() {}
  16. }

存取器装饰器(Accessor Decorator)

  1. /**
  2. * 定义一个存取器装饰器
  3. *
  4. * @param {Function} target 如果装饰是静态方法,则为类的构造器(constructor); 如果被装饰的是实例方法,则为类的原型
  5. * @param {string} propertyKey
  6. * @param {PropertyDescriptor} descriptor 被装饰的方法的描述符
  7. */
  8. function getterSetterDecorator(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  9. }
  10. // -- 使用存取器装饰器 -- //
  11. class ClassToBeDecorated {
  12. @getterSetterDecorator
  13. get getterToBeDecorated(){}
  14. @getterSetterDecorator
  15. set setterToBeDecorated() {}
  16. }

属性装饰器(Property Decorator)

  1. /**
  2. * 定义一个属性装饰器
  3. *
  4. * @param {Function} target 如果装饰是静态属性,则为类的构造器(constructor); 如果被装饰的是实例属性,则为类的原型
  5. * @param {string} propertyName 属性的名字
  6. */
  7. function propertyDecorator(target: any, propertyName: string ) {
  8. }
  9. // -- 使用属性装饰器 -- //
  10. class ClassToBeDecorated {
  11. @propertyDecorator
  12. public propertyToBeDecoreated: string = '1';
  13. }

变量装饰器(Parameter Decorator)

  1. /**
  2. * 定义一个变量装饰器
  3. *
  4. * @param {Function} target 如果装饰是静态属性,则为类的构造器(constructor); 如果被装饰的是实例属性,则为类的原型
  5. * @param {string | symbol} key 变量参数的键值,通常为该变量的名字,也可以是symbol
  6. * @param {number} index 被装饰的变量在变量列表中的索引
  7. */
  8. function parameterDecorator(target: Object, key: string | symbol, index: number) {
  9. }
  10. // -- 使用变量装饰器 -- //
  11. class ClassToBeDecorated {
  12. constructor(private @parameterDecorator parameterToBeDecorated: string) {
  13. }
  14. method(private @parameterDecorator parameterToBeDecorated: string) {
  15. }
  16. }

装饰器工厂

装饰器工厂就是生产装饰器函数的函数。

一个装饰器通常只完成某一个功能,为了使得装饰器支持根据传入的变量改变不同的功能,这个时候就需要装饰器工厂。

Typescript中的装饰器生成代码分析

  1. const __decorate =
  2. // 检测__decorate方法是否存在,防止反复创建
  3. (this && this.__decorate) ||
  4. // decorators 装饰器列表
  5. function (decorators, target, key, desc) {
  6. // 从上文了解到不同的装饰器函数接收的参数数量是不同
  7. // 其中 【方法装饰器】 和 【存取器装饰器】的入参变量数量是3个,其中第三个参数是属性描述符
  8. // 这里判断参数数量大于3个的时候,通过getOwnPropertyDescriptor获取属性描述符
  9. const c = arguments.length;
  10. let r =
  11. c < 3
  12. ? target
  13. : desc === null
  14. ? (desc = Object.getOwnPropertyDescriptor(target, key))
  15. : desc;
  16. // 如果支持反射,那么直接使用反射对象提供的decorate方法
  17. // 如果不存在,那么就把装饰器函数作为普通的函数,把对应的参数传入。
  18. let d;
  19. if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') {
  20. r = Reflect.decorate(decorators, target, key, desc);
  21. } else {
  22. // 从后向前遍历装饰器并组合调用
  23. // 比如装饰器 @a @b @c 这样装饰顺序,那么就相当于这样的调用: a(b(c()))
  24. for (let i = decorators.length - 1; i >= 0; i--) {
  25. if ((d = decorators[i])) {
  26. r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  27. }
  28. }
  29. }
  30. return c > 3 && r && Object.defineProperty(target, key, r), r;
  31. };
  32. // 添加元数据
  33. const __metadata =
  34. (this && this.__metadata) ||
  35. function (k, v) {
  36. if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function') {
  37. return Reflect.metadata(k, v);
  38. }
  39. };
  40. // 【变量装饰器】比较特殊,虽然都拥有3个参数,但是第3个参数不是属性描述符,而是变量的索引位置
  41. const __param =
  42. (this && this.__param) ||
  43. function (paramIndex, decorator) {
  44. return function (target, key) {
  45. decorator(target, key, paramIndex);
  46. };
  47. };