浅谈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装饰器使用方法的资料请关注脚本之家其它相关文章!
