深入理解TS装饰器(Deep-in Decorator)

1,322 阅读3分钟

前言

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

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

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

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

如何定义装饰器

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

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

类装饰器

/**
 * 定义一个类装饰器
 * @param {Function} constructor 被装饰的类的构造器
 */
function classDecorator(constructor: Function) {
  // 对被装饰的类做各种操作
}

// -- 使用类装饰器 -- //

@classDecorator
class ClassToBeDecorated {}

方法装饰器

/**
 * 定义一个方法装饰器
 *
 * @param {Function} target 如果装饰是静态方法,则为类的构造器(constructor); 如果被装饰的是实例方法,则为类的原型
 * @param {string} propertyKey 
 * @param {PropertyDescriptor} descriptor 被装饰的方法的描述符
 */
function methodDecorator(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
}

// -- 使用方法装饰器 -- //

class ClassToBeDecorated {
	
  @methodDecorator
  methodToBeDecorated(){}
  
  @methodDecorator
  static staticMethodToBeDecorated() {}
}
  

存取器装饰器(Accessor Decorator)


/**
 * 定义一个存取器装饰器
 *
 * @param {Function} target 如果装饰是静态方法,则为类的构造器(constructor); 如果被装饰的是实例方法,则为类的原型
 * @param {string} propertyKey 
 * @param {PropertyDescriptor} descriptor 被装饰的方法的描述符
 */
function getterSetterDecorator(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
}

// -- 使用存取器装饰器 -- //

class ClassToBeDecorated {
	
  @getterSetterDecorator
  get getterToBeDecorated(){}
  
  @getterSetterDecorator
  set setterToBeDecorated() {}
}

属性装饰器(Property Decorator)


/**
 * 定义一个属性装饰器
 *
 * @param {Function} target 如果装饰是静态属性,则为类的构造器(constructor); 如果被装饰的是实例属性,则为类的原型
 * @param {string} propertyName 属性的名字
 */
function propertyDecorator(target: any, propertyName: string ) {
}

// -- 使用属性装饰器 -- //

class ClassToBeDecorated {

  @propertyDecorator
  public propertyToBeDecoreated: string = '1';
}

变量装饰器(Parameter Decorator)


/**
 * 定义一个变量装饰器
 *
 * @param {Function} target 如果装饰是静态属性,则为类的构造器(constructor); 如果被装饰的是实例属性,则为类的原型
 * @param {string | symbol} key 变量参数的键值,通常为该变量的名字,也可以是symbol
 * @param {number} index 被装饰的变量在变量列表中的索引
 */
function parameterDecorator(target: Object, key: string | symbol, index: number) {
}

// -- 使用变量装饰器 -- //

class ClassToBeDecorated {

  constructor(private @parameterDecorator parameterToBeDecorated: string) {
  }
 
  method(private @parameterDecorator parameterToBeDecorated: string) {
  }
}

装饰器工厂

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

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

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

const __decorate =
  // 检测__decorate方法是否存在,防止反复创建
  (this && this.__decorate) ||
      
  // decorators 装饰器列表  
  function (decorators, target, key, desc) {
    
    // 从上文了解到不同的装饰器函数接收的参数数量是不同
    // 其中 【方法装饰器】 和 【存取器装饰器】的入参变量数量是3个,其中第三个参数是属性描述符
    // 这里判断参数数量大于3个的时候,通过getOwnPropertyDescriptor获取属性描述符
    const c = arguments.length;
    let r =
      c < 3
        ? target
        : desc === null
        ? (desc = Object.getOwnPropertyDescriptor(target, key))
        : desc;
    
    // 如果支持反射,那么直接使用反射对象提供的decorate方法
    // 如果不存在,那么就把装饰器函数作为普通的函数,把对应的参数传入。
    let d;
    if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') {
      r = Reflect.decorate(decorators, target, key, desc);
    } else {
      // 从后向前遍历装饰器并组合调用
      // 比如装饰器 @a @b @c 这样装饰顺序,那么就相当于这样的调用: a(b(c()))
      for (let i = decorators.length - 1; i >= 0; i--) {
        if ((d = decorators[i])) {
          r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        }
      }
    }
    
    return c > 3 && r && Object.defineProperty(target, key, r), r;
  };

// 添加元数据
const __metadata =
  (this && this.__metadata) ||
  function (k, v) {
    if (typeof Reflect === 'object' && typeof Reflect.metadata === 'function') {
      return Reflect.metadata(k, v);
    }
  };

// 【变量装饰器】比较特殊,虽然都拥有3个参数,但是第3个参数不是属性描述符,而是变量的索引位置
const __param =
  (this && this.__param) ||
  function (paramIndex, decorator) {
    return function (target, key) {
      decorator(target, key, paramIndex);
    };
  };