装饰器与元数据反射(3)参数装饰器

以前已经分别介绍了方法装饰器属性装饰器和类装饰器,这篇文章咱们来继续关注这些话题:segmentfault

  • 参数装饰器
  • 装饰器工厂

咱们将围绕如下这个例子,来探讨这些概念:数组

class Person { 

  public name: string;
  public surname: string;

  constructor(name : string, surname : string) { 
    this.name = name;
    this.surname = surname;
  }

  public saySomething(something : string) : string { 
    return this.name + " " + this.surname + " says: " + something; 
  }
}

参数装饰器

TypeScript对于参数装饰器的声明以下app

declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

以下咱们为类PersonsaySomething方法的参数添加一个参数装饰器函数

public saySomething(@logParameter something : string) : string { 
    return this.name + " " + this.surname + " says: " + something; 
}

最终被编译为JavaScript的样子为:this

Object.defineProperty(Person.prototype, "saySomething",
    __decorate(
        [__param(0, logParameter)],
        Person.prototype,
        "saySomething",
        Object.getOwnPropertyDescriptor(Person.prototype, "saySomething")
    )
);
return Person;

若是将其和以前的装饰器比较,是否会发现又使用了Object.defineProperty()方法,那么是否意味着saySomething将被__decorated函数的返回值替换?prototype

咱们发现这里有个新函数__param,TypeScript编译器生成以下:code

var __param = this.__param || function (index, decorator) {
    // 返回一个装饰器函数
    return function (target, key) {
        // 应用装饰器(忽略返回值)
        decorator(target, key, index); 
    }
};

如上所示,调用参数装饰器,其并无返回值,这就意味着,函数__decorate的调用返回并无覆盖方法saySomething,也很好理解:参数装饰器要毛返回。索引

可见参数装饰器函数须要3个参数:被装饰类的原型,装饰参数所属的方法名,参数的索引。具体的实现以下:ip

function logParameter(target: any, key : string, index : number) {
  var metadataKey = `log_${key}_parameters`;
  if (Array.isArray(target[metadataKey])) {
    target[metadataKey].push(index);
  }
  else { 
    target[metadataKey] = [index];
  }
}

其中向类的原型中增长一个新的属性metadataKey,该属性值是一个数组,包含所装饰参数的索引,能够把它看成元数据。get

参数装饰器不该当用来修改构造器、方法或属性的行为,它只应当用来产生某种元数据。一旦元数据被建立,咱们即可以用其它的装饰器去读取它。

装饰器工厂

官方TypeScript装饰器建议定义一个以下的装饰器工厂:

装饰器工厂首先是一个函数,它接受任意数量的参数,同时返回如前所述的四种之一特定类型的装饰器。

虽然已经讨论四种装饰是如何实现及使用的,但仍是有一些能够改进的地方,观察下面的代码片断:

@logClass
class Person { 

  @logProperty
  public name: string;

  public surname: string;

  constructor(name : string, surname : string) { 
    this.name = name;
    this.surname = surname;
  }

  @logMethod
  public saySomething(@logParameter something : string) : string { 
    return this.name + " " + this.surname + " says: " + something; 
  }
}

这里装饰器的使用是没问题的,但若是咱们能够不关心装饰器的类型,而在任何地方使用岂不方便,就像下面的样子:

@log
class Person { 

  @log
  public name: string;

  public surname: string;

  constructor(name : string, surname : string) { 
    this.name = name;
    this.surname = surname;
  }

  @log
  public saySomething(@log something : string) : string { 
    return this.name + " " + this.surname + " says: " + something; 
  }
}

这边是装饰器工厂的使用诉求,它能够识别具体状况下该使用哪一种类型的装饰器,幸运的是,咱们能够经过传递给装饰器的参数来区分它的类型。

function log(...args : any[]) {
  switch(args.length) {
    case 1:
      return logClass.apply(this, args);
    case 2:
      return logProperty.apply(this, args);
    case 3:
      if(typeof args[2] === "number") {
        return logParameter.apply(this, args);
      }
      return logMethod.apply(this, args);
    default:
      throw new Error("Decorators are not valid here!");
  }
}
相关文章
相关标签/搜索