NestJS 学习笔记-02-装饰器

277 阅读5分钟

概念

在学习 nestjs 语法之前,要先了解装饰器的概念。随着 TypeScriptES6 中类的引入,存在某些需要附加功能来支持注释或修改类和类成员的场景。 装饰器提供了一种为类声明和成员添加注释和元编程语法的方法。装饰器本质上是一种特殊的函数,它可以被应用在:

  • 类方法
  • 类属性
  • 类访问器
  • 类方法的参数

启用装饰器

要想启用TypeScript对装饰器的支持,需要在tsconfig.json文件中将experimentalDecorators属性设置为true

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

装饰器

装饰器本质上是一个函数,因此我们首先定义一个函数:

function decorator(target: any) {
  console.log("装饰器被调用了");
}

然后,我们将这个函数作为装饰器在类上使用,具体用法为@符号后面加上装饰器函数名:

@decorator
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

执行这段代码,控制台输出:

普通装饰器.png

可以看到无需 new 一个对象,就会触发装饰器函数,以上只是装饰器的一个简单用法,如果我们想在装饰器函数中使用参数,就要使用装饰器工厂了。

装饰器工厂

装饰器工厂是一个返回装饰器的函数,它可以接收参数,并返回一个装饰器:

function decoratorFactory(msg: string) {
  return function (target: any) {
    console.log(msg);
  };
}

同样的我们把它应用到类上:

@decoratorFactory("装饰器工厂")
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

执行这段代码,控制台输出:

装饰器工厂.png

装饰器工厂可以更灵活的实现一些功能的扩展,用的也是最多的。接下来的我们均已装饰器工厂来演示。

装饰器的执行顺序

不同类型的装饰器的执行顺序是明确定义的:

从作用的位置上看,执行顺序为:

  • 实例成员 > 静态成员 > 构造器参数 > 类

而在实例成员和静态成员中,执行顺序为:

  • 参数装饰器 > 方法/访问器/属性装饰器

具体可以通过一段代码来看:

function f(key: string): any {
  console.log("定义: ", key);
  return function () {
    console.log("调用: ", key);
  };
}

@f("类")
class C {
  @f("静态属性")
  static prop: string;

  @f("静态方法")
  static method(@f("静态方法的参数") prop: string) {}

  constructor(@f("构造器的参数") prop: string) {
    this.prop = prop;
  }

  @f("实例方法")
  method(@f("实例方法的参数") prop: string) {}

  @f("实例属性")
  prop: string;
}

执行这段代码;

执行顺序.png

从图中可以看出,实例方法在实例属性之前被调用,而静态属性则在静态方法之前被调用。这是因为方法/访问器/属性装饰器并没有优先级,它们的执行顺序取决于他们的定义顺序。

而在同一方法或构造函数中参数的装饰器的执行顺序却相反,后面的参数会被优先调用装饰器:

function f(key: string): any {
  console.log("定义: ", key);
  return function () {
    console.log("调用: ", key);
  };
}

class C {
  method(@f("参数1") foo, @f("参数2") bar) {}
}

参数执行顺序.png

如果多个装饰器作用到一个目标上,那么他们的执行顺序为从下到上,即先定义的后执行。具体看代码:

function f1(key: string): any {
  console.log("定义: ", key);
  return function () {
    console.log("调用: ", key);
  };
}
function f2(key: string): any {
  console.log("定义: ", key);
  return function () {
    console.log("调用: ", key);
  };
}

@f1("装饰器1")
@f2("装饰器2")
class C {
  constructor() {}
}

组合装饰器的执行顺序.png

装饰器的类别

类装饰器

  • 参数:类装饰器接收一个参数,该参数为类的构造函数。
  • 返回值:如果类装饰器返回一个值,那么这个值将被用来替换类的定义。
  • 示例:
function classDecorator(): ClassDecorator {
  return function <TFunction extends Function>(
    target: TFunction
  ): TFunction | void {
    console.log(target);
    // 修改原型对象上的toString方法
    target.prototype.toString = function () {
      return JSON.stringify(this);
    };
    return target;
  };
}

@classDecorator()
class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

console.log(new User("aaa").toString());

类装饰器.png

方法装饰器

  • 参数:方法装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是方法的名字,最后一个是方法的属性描述符。
  • 返回值:如果返回一个值,它将被用作方法的属性描述符
  • 示例:
function methodDecorator(): MethodDecorator {
  return function (
    target: Object,
    key: string | symbol,
    descriptor: PropertyDescriptor
  ) {
    console.log(target);
    console.log(key);
    console.log(descriptor);
    // 修改getName的返回值
    descriptor.value = function getName() {
      return "bbb";
    };
    return descriptor;
  };
}

class User {
  @methodDecorator()
  getName() {
    return "aaa";
  }
}

console.log(new User().getName());

方法装饰器.png

访问符装饰器

  • 参数:访问符装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是访问符的名字,最后一个是访问符的属性描述符。
  • 返回值:如果返回一个值,它将被用作访问符的属性描述符
  • 示例:
function accessorDecorator(): MethodDecorator {
  return function (
    target: Object,
    key: string | symbol,
    descriptor: PropertyDescriptor
  ) {
    console.log(target);
    console.log(key);
    console.log(descriptor);
    return descriptor;
  };
}

class User {
  private _name: string = "aaa";
  @accessorDecorator()
  get name(): string {
    return this._name;
  }

  set name(val: string d) {
    this._name = val;
  }
}

console.log(new User().name);

访问符装饰器.png

访问符也是一种方法装饰器,只不过它作用于访问符而不是方法,他与方法的唯一区别就是属性描述符不同。

属性装饰器

  • 参数:属性装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是属性的名字。
  • 返回值:返回值会被忽略
  • 示例:
function propertyDecorator(): PropertyDecorator {
  return function (target: Object, key: string | symbol): void {
    console.log(target);
    console.log(key);
  };
}

class User {
  @propertyDecorator()
  static age: number = 3;
}

console.log(User.age);

属性装饰器.png

参数装饰器

  • 参数:参数装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是属性的名称(方法的名称,而不是参数),第三个是方法的参数的索引。
  • 返回值:返回值会被忽略
  • 示例:
function parameterDecorator(): ParameterDecorator {
  return function (
    target: Object,
    key: string | symbol | undefined,
    index: number
  ): void {
    console.log(target);
    console.log(key);
    console.log(index);
  };
}
class User {
  name: string;
  constructor(@parameterDecorator() name: string) {
    this.name = name;
  }
  getName(name: string, @parameterDecorator() age: number) {
    return name + age;
  }
}

参数装饰器.png