浅谈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
中配置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); 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 {}
因为需要接受额外的信息,所以这必然是需要一个函数调用的形式。这里 @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装饰器使用方法的资料请关注脚本之家其它相关文章!
相关文章
JavaScript组合模式Composite Pattern
这篇文章主要介绍了学习理解JavaScript组合模式,组合模式及Composite Pattern又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象2022-04-04
最新评论