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

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

前言

在最近的开发中,阅读到了前辈留下的装饰器代码,因为自己对装饰器不太了解所以学习了一下。本文只是作为学习后的心得分享和记录。本文只讨论类装饰器的语法与在TS中的使用。如有不足烦请各位大佬指出!感恩~

类装饰器

什么是类装饰器呢?

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

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

    }
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 {
}
    type decoratorType = new (...args:any[]) => object;
    
    function decorator ( target : decorarorType ) {
      
    };
    

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

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

类装饰器的运行时机

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

image.png

运行后发现,在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);
  // 这里有一个函数 他的第一个参数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);
// 这里有一个函数 他的第一个参数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);

装饰器的返回值

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

  • void
  • 一个新的类:会将新的类替换掉装饰目标

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

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

image.png

这是因为装饰器是一个通用的,虽然在当前场景下因为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 {}
type decoratorType = new (...args: any) => object;





function decorator(str: string) {
  return function (target: decoratorType) {
    console.log(str);
    console.log(target);
  };

}



@decorator("这是一个类")
class A {}
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。我们可以看一下运行结果。

image.png

多个装饰器的情况

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

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 {}
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 {}
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 {}

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

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

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;
};
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;
  };
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 {}
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 {}
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 {}

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

image.png

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

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
);
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
);
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 );

结尾

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

好了,今天的学习和分享就到这里,如有不足或错误之处也请大家多包涵。告辞!

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYwFWGhs' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片