javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS装饰器使用方法

浅谈JS装饰器以及装饰器在TS中的使用方式

作者:菜鸡夯师傅

这篇文章主要带大家探讨一下JS装饰器以及装饰器在TS中的使用方式,文中有详细的代码示例,对装饰器不太了解的同学可以参考阅读本文学习了一下

类装饰器

什么是类装饰器呢?

类装饰器的本质是一个函数,该函数接受一个参数,表示类本身(构造函数本身)。 那么类装饰器该如何使用呢?

    function decorator (target) {
    }
    @decorator
    class A {
    }

这样的话就完成了类装饰器的编写。不难看出类装饰器的调用方式就是@函数名的方式放在一个类的声明之前。那如果在TS中使用的话,我们都知道TS中有类型检查,那对于装饰器而言,主要在形参的类型上需要我们自己定义。那类装饰器的形参表示的是类本身,该如何定义呢?

我们知道JS中的类其实就是一个函数,所以我们可以使用Function来对类进行类型定义。但是这不是很严谨,因为类是可以用new关键字来声明的,并且会返回一个object,所以更推荐使用new (参数) => object来定义类的类型。那么以上代码就会被改造成这样:

    type decoratorType = new (...args:any[]) => object;
    function decorator ( target : decorarorType ) {
    };
    @decorator;
    class A {
    }

值得注意的是,ts可能会在类名处有错误提示,这是因为装饰器在ts中还处于试验阶段。我们可以在 tsconfig.json中配置experimentalDecoratorstrue来规避这个报错。

类装饰器的运行时机

类装饰器的运行时间是在类定义后直接运行。我们可以验证一下:

运行后发现,在class A定义完成后立即就输出了我是decorator

从编译结果上也能很容易的看出,装饰器是在类定义后直接调用的:

  // 这里有一个函数 他的第一个参数decorators表示装饰器数组,第二个参数target表示被装饰的类本身。
  var __decorate =
  (this && this.__decorate) ||
  function (decorators, target, key, desc) {
    var c = arguments.length,
      r =
        c < 3
          ? target
          : desc === null
          ? (desc = Object.getOwnPropertyDescriptor(target, key))
          : desc,
      d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
     // 调用装饰器
      r = Reflect.decorate(decorators, target, key, desc);
    else
      for (var 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;
  };
// 这时我们定义的装饰器函数
function decotator(target) {
  console.log("我是decorator");
}
// 定义类
let A = class A {};
// 装饰器和类作为参数传入__decorate中运行。
A = __decorate([decotator], A);

装饰器的返回值

装饰器可以有以下几种返回值:

这里具体演示第二种返回值----返回一个新的类。在装饰器中返回新的类class B。此时new一个class A的实例,打印后发现为class B的实例对象。说明class A通过装饰器已经被替换为class B了。

所以说在装饰器中可以通过以上方式,增强被装饰类的功能。不过在ts中这种方式可能丢失一些类型检查。如下图:

这是因为装饰器是一个通用的,虽然在当前场景下因为class B继承了装饰器传入的class A,因为类装饰器的入参是动态的,所以ts并不能知道到底有没有prop1这个属性。当然这样并不会影响代码的功能,只是在ts中会丢失类型检查。

那么如果我想让装饰器能够接受一些额外的内容,该怎么做呢?在实际开发中,可能需要某些数据来参与逻辑。那么我们可以用以下方式来实现:

type decoratorType = new (...args: any) => object;
function decorator(str: string) {
  return function (target: decoratorType) {
    console.log(str);
    console.log(target);
  };
}
@decorator("这是一个类")
class A {}

因为需要接受额外的信息,所以这必然是需要一个函数调用的形式。这里 @decorator接受了一个字符串参数。并且decorator函数接受一个形参str且返回一个新的函数,所以decorator函数返回的函数会作为真正的装饰器,它可以接收到被修饰类class A。我们可以看一下运行结果。

多个装饰器的情况

那么如果有多个装饰器呢?他们的运行顺序是我们想的那样从上到下依次运行的吗?比如有以下代码:

type decoratorType = new (...args: any) => object;
function decorator1(target: decoratorType) {
  console.log("我是装饰器1");
}
function decorator2(target: decoratorType) {
  console.log("我是装饰器2");
}
function decorator3(target: decoratorType) {
  console.log("我是装饰器3");
}
@decorator1
@decorator2
@decorator3
class A {}

按照我们一贯的思维,因为是同步代码,所以会按顺序执行,事实真的是这样吗?让我们来看一下运行结果:

不难发现装饰器的运行顺序是遵循后加入先调用的形式!从编译结果也能发现:

var __decorate =
  (this && this.__decorate) ||
  function (decorators, target, key, desc) {
    var c = arguments.length,
      r =
        c < 3
          ? target
          : desc === null
          ? (desc = Object.getOwnPropertyDescriptor(target, key))
          : desc,
      d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
     // 调用装饰器
      r = Reflect.decorate(decorators, target, key, desc);
    else
      for (var i = decorators.length - 1; i >= 0; i--)
       //看这里 调用装饰器(从后往前因为是i--且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;
  };

那如果把装饰器作为一个函数来调用呢?

type decoratorType = new (...args: any) => object;
function decorator1(str: string) {
  console.log("第一个函数运行了");
  return function (target: decoratorType) {
    console.log(str);
  };
}
function decorator2(str: string) {
  console.log("第二个函数运行了");
  return function (target: decoratorType) {
    console.log(str);
  };
}
function decorator3(str: string) {
  console.log("第三个函数运行了");
  return function (target: decoratorType) {
    console.log(str);
  };
}
@decorator1("我是装饰器1")
@decorator2("我是装饰器2")
@decorator3("我是装饰器3")
class A {}

当作为函数调用时,会先执行函数体,因为该函数又返回了一个函数,所以返回的新函数会作为装饰器。运行第一个函数会的到第一个装饰器,以此类推会获得三个装饰器。而装饰器是按照后加入先调用的形式,所以会输出以下结果:

从编译结果来看能更好的理解:

function decorator1(str) {
  console.log("第一个函数运行了");
  return function (target) {
    console.log(str);
  };
}
function decorator2(str) {
  console.log("第二个函数运行了");
  return function (target) {
    console.log(str);
  };
}
function decorator3(str) {
  console.log("第三个函数运行了");
  return function (target) {
    console.log(str);
  };
}
let A = class A {};
A = __decorate(
  [
    decorator1("我是装饰器1"), // 第一个函数的返回结果作为装饰器
    decorator2("我是装饰器2"), // 第二个函数的返回结果作为装饰器
    decorator3("我是装饰器3"), // 第三个函数的返回结果作为装饰器
  ],
  A
);

结尾

如果读完这篇文章能够帮助你更好的理解类装饰器,欢迎留言讨论、点赞收藏。

以上就是浅谈JS装饰器以及装饰器在TS中的使用方式的详细内容,更多关于JS装饰器使用方法的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文