概念
在学习 nestjs 语法之前,要先了解装饰器的概念。随着 TypeScript 和 ES6 中类的引入,存在某些需要附加功能来支持注释或修改类和类成员的场景。 装饰器提供了一种为类声明和成员添加注释和元编程语法的方法。装饰器本质上是一种特殊的函数,它可以被应用在:
- 类
- 类方法
- 类属性
- 类访问器
- 类方法的参数
启用装饰器
要想启用TypeScript对装饰器的支持,需要在tsconfig.json文件中将experimentalDecorators属性设置为true。
{
"compilerOptions": {
"experimentalDecorators": true
}
}
装饰器
装饰器本质上是一个函数,因此我们首先定义一个函数:
function decorator(target: any) {
console.log("装饰器被调用了");
}
然后,我们将这个函数作为装饰器在类上使用,具体用法为@符号后面加上装饰器函数名:
@decorator
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
执行这段代码,控制台输出:
可以看到无需 new 一个对象,就会触发装饰器函数,以上只是装饰器的一个简单用法,如果我们想在装饰器函数中使用参数,就要使用装饰器工厂了。
装饰器工厂
装饰器工厂是一个返回装饰器的函数,它可以接收参数,并返回一个装饰器:
function decoratorFactory(msg: string) {
return function (target: any) {
console.log(msg);
};
}
同样的我们把它应用到类上:
@decoratorFactory("装饰器工厂")
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
执行这段代码,控制台输出:
装饰器工厂可以更灵活的实现一些功能的扩展,用的也是最多的。接下来的我们均已装饰器工厂来演示。
装饰器的执行顺序
不同类型的装饰器的执行顺序是明确定义的:
从作用的位置上看,执行顺序为:
- 实例成员 > 静态成员 > 构造器参数 > 类
而在实例成员和静态成员中,执行顺序为:
- 参数装饰器 > 方法/访问器/属性装饰器
具体可以通过一段代码来看:
function f(key: string): any {
console.log("定义: ", key);
return function () {
console.log("调用: ", key);
};
}
@f("类")
class C {
@f("静态属性")
static prop: string;
@f("静态方法")
static method(@f("静态方法的参数") prop: string) {}
constructor(@f("构造器的参数") prop: string) {
this.prop = prop;
}
@f("实例方法")
method(@f("实例方法的参数") prop: string) {}
@f("实例属性")
prop: string;
}
执行这段代码;
从图中可以看出,实例方法在实例属性之前被调用,而静态属性则在静态方法之前被调用。这是因为方法/访问器/属性装饰器并没有优先级,它们的执行顺序取决于他们的定义顺序。
而在同一方法或构造函数中参数的装饰器的执行顺序却相反,后面的参数会被优先调用装饰器:
function f(key: string): any {
console.log("定义: ", key);
return function () {
console.log("调用: ", key);
};
}
class C {
method(@f("参数1") foo, @f("参数2") bar) {}
}
如果多个装饰器作用到一个目标上,那么他们的执行顺序为从下到上,即先定义的后执行。具体看代码:
function f1(key: string): any {
console.log("定义: ", key);
return function () {
console.log("调用: ", key);
};
}
function f2(key: string): any {
console.log("定义: ", key);
return function () {
console.log("调用: ", key);
};
}
@f1("装饰器1")
@f2("装饰器2")
class C {
constructor() {}
}
装饰器的类别
类装饰器
- 参数:类装饰器接收一个参数,该参数为类的构造函数。
- 返回值:如果类装饰器返回一个值,那么这个值将被用来替换类的定义。
- 示例:
function classDecorator(): ClassDecorator {
return function <TFunction extends Function>(
target: TFunction
): TFunction | void {
console.log(target);
// 修改原型对象上的toString方法
target.prototype.toString = function () {
return JSON.stringify(this);
};
return target;
};
}
@classDecorator()
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
console.log(new User("aaa").toString());
方法装饰器
- 参数:方法装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是方法的名字,最后一个是方法的属性描述符。
- 返回值:如果返回一个值,它将被用作方法的属性描述符
- 示例:
function methodDecorator(): MethodDecorator {
return function (
target: Object,
key: string | symbol,
descriptor: PropertyDescriptor
) {
console.log(target);
console.log(key);
console.log(descriptor);
// 修改getName的返回值
descriptor.value = function getName() {
return "bbb";
};
return descriptor;
};
}
class User {
@methodDecorator()
getName() {
return "aaa";
}
}
console.log(new User().getName());
访问符装饰器
- 参数:访问符装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是访问符的名字,最后一个是访问符的属性描述符。
- 返回值:如果返回一个值,它将被用作访问符的属性描述符
- 示例:
function accessorDecorator(): MethodDecorator {
return function (
target: Object,
key: string | symbol,
descriptor: PropertyDescriptor
) {
console.log(target);
console.log(key);
console.log(descriptor);
return descriptor;
};
}
class User {
private _name: string = "aaa";
@accessorDecorator()
get name(): string {
return this._name;
}
set name(val: string d) {
this._name = val;
}
}
console.log(new User().name);
访问符也是一种方法装饰器,只不过它作用于访问符而不是方法,他与方法的唯一区别就是属性描述符不同。
属性装饰器
- 参数:属性装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是属性的名字。
- 返回值:返回值会被忽略
- 示例:
function propertyDecorator(): PropertyDecorator {
return function (target: Object, key: string | symbol): void {
console.log(target);
console.log(key);
};
}
class User {
@propertyDecorator()
static age: number = 3;
}
console.log(User.age);
参数装饰器
- 参数:参数装饰器接收三个参数,第一个是静态成员的类的构造函数或实例成员的类的原型对象、第二个是属性的名称(方法的名称,而不是参数),第三个是方法的参数的索引。
- 返回值:返回值会被忽略
- 示例:
function parameterDecorator(): ParameterDecorator {
return function (
target: Object,
key: string | symbol | undefined,
index: number
): void {
console.log(target);
console.log(key);
console.log(index);
};
}
class User {
name: string;
constructor(@parameterDecorator() name: string) {
this.name = name;
}
getName(name: string, @parameterDecorator() age: number) {
return name + age;
}
}