# 装饰器

装饰器是一项实验性特性,若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项。

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

# 装饰器定义

装饰器是一种特殊类型的函数,可以用来修饰类,属性和方法。可以在不修改类,属性和方法的前提下扩展类,属性和方法的功能。
装饰器使用@expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。

// 定义一个装饰器
function persons(target){
  console.log('target',target);
}
// 在类的上面使用装饰器
@persons
class PersonClass{}

在上面的代码中,person函数作为一个装饰器,如果我们想要使用装饰器,只需要在对应的类上面,使用@person注意,这里来的person必须是一个函数。

# 装饰器的功能——扩展类的功能

我们使用装饰器的最大的特点就是为了拓展类的功能,装饰器中的参数target就是要装饰的类PersonClass(){},接下来我们尝试给类拓展功能。示例:

// 定义一个装饰器
function persons(target){
  // 给装饰器修饰的类拓展方法
  target.prototype.name = "装饰器";
  target.prototype.showName = function(){
    return this.name;
  }
}

@persons
class PersonClass{}
let p1 = new PersonClass();
console.log(p1.name);  // 装饰器
console.log(p1.showName()); //装饰器

在上面的代码中,我们给装饰器修饰的类拓展了一个属性name和方法showName。实例化以后我们发现可以正常地调用这些属性和方法。这就相当于拓展了类的功能。

# 装饰器运行时机

装饰器对类的行为的改变,是代码编译时发生的(不是TypeScript编译,而是js在执行机中编译阶段),而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。

# 装饰器的分类

刚刚在上面中,我们使用的装饰器都是简单的装饰器,但是事实上如果我们需要拓展类的功能,通常需要传入一些参数,这时候就需要有参装饰器了。在typecript中通过装饰器工厂来实现装饰器参数的传递。

function func(value){
  return (target) => {
    console.log(value);
    console.log(target)
  }
};

@func('hello')
class PersonClass{}

从上面的代码中,我们给装饰器传递了参数@func('hello')func是一个工厂函数,它可以接收参数,同时返回一个函数。返回的函数作为装饰器。

# 属性装饰器

属性装饰器声明在一个属性声明之前(紧靠着属性声明)。

function prop(value:string|number){
  return (target:any,attr:any):void => {
    target[attr] = value;
  }
};

class Index{
  @prop('hello')
  static sex:string;
  @prop(27)
  public age:number;
  constructor(){};
}
let index = new Index();
console.log(Index.sex);   // hello
console.log(index.age);   // 27
  1. 如果是属性装饰器,那么声明在属性声明之前。
  2. 属性装饰器有两个参数,targetattr。如果修饰的是静态属性,那么target是类,如果修饰的是实例属性,那么target是实例成员。attr是对应的属性。

# 方法装饰器

属性装饰器声明在一个方法声明之前(紧靠着方法声明)。方法装饰器接收三个参数。

  1. 对于静态方法来说是类的构造函数,对于实例方法是类的原型对象。
  2. 方法名称
  3. 方法的属性描述符{writable: true, enumerable: true, configurable: true, value: ƒ}
function get(){
  return (target:any, methodName:string, desc:any) => {
    console.log(target);
    console.log(methodName);
    console.log(desc);
    // 修改原来的方法
    desc.value = (arr) =>{
      const str = arr.join();
      return str;
    }
  }
}

class Http{
  @get()
  get(arr){
    return arr;
  }
}

# 总结

装饰器的特点

  1. 本质是一个函数
  2. 装饰器的功能就是用于拓展类的功能,比如给类的成员,方法等拓展功能
  3. 在编译时执行,而不是类实例话或者调用时执行
  4. 装饰器的使用,关键是看需要几个参数,然后根据需要修饰的对象来实现功能即可