浅谈JS装饰器以及装饰器在TS中的使用方式
前言
在最近的开发中,阅读到了前辈留下的装饰器代码,因为自己对装饰器不太了解所以学习了一下。本文只是作为学习后的心得分享和记录。本文只讨论类装饰器的语法与在TS中的使用。如有不足烦请各位大佬指出!感恩~
类装饰器
什么是类装饰器呢?
类装饰器的本质是一个函数
,该函数接受一个参数,表示类本身(构造函数本身)
。
那么类装饰器该如何使用呢?
function decorator (target) {}@decoratorclass 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
中配置experimentalDecorators
为true
来规避这个报错。
类装饰器的运行时机
类装饰器的运行时间是在类定义后直接运行
。我们可以验证一下:
运行后发现,在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);elsefor (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
了。
所以说在装饰器中可以通过以上方式,增强被装饰类的功能。不过在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 {}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
。我们可以看一下运行结果。
多个装饰器的情况
那么如果有多个装饰器呢?他们的运行顺序是我们想的那样从上到下依次运行的吗?比如有以下代码:
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@decorator3class 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 {}
按照我们一贯的思维,因为是同步代码,所以会按顺序执行,事实真的是这样吗?让我们来看一下运行结果:
不难发现装饰器的运行顺序是遵循后加入先调用
的形式!从编译结果也能发现:
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);elsefor (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 {}
当作为函数调用时,会先执行函数体
,因为该函数又返回了一个函数
,所以返回的新函数会作为装饰器
。运行第一个函数会的到第一个装饰器,以此类推会获得三个装饰器。而装饰器是按照后加入先调用
的形式,所以会输出以下结果:
从编译结果来看能更好的理解:
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 );
结尾
如果读完这篇文章能够帮助你更好的理解类装饰器,欢迎留言讨论、点赞收藏。
好了,今天的学习和分享就到这里,如有不足或错误之处也请大家多包涵。告辞!