装饰器是一种特殊类型的声明,可以用来“装饰”三种类型的对象:类的属性/方法、访问器、类本身。装饰器使用@expression
这种形式,expression求值后必须为一个函数,称为装饰器工厂函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数 注意装饰器的调用顺序:
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
参数装饰器应用到构造函数。
类装饰器应用到类。
参数装饰器会在方法装饰器之前调用,对参数进行注解,外部装饰器可以拿到注解。
解析现代koa装饰器 下面来一段装饰器写法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Path, Get, Post, Param, Query, Body } from 'koa-decorate' ;
@Path ('/api/cat' )
class CatController {
@Get
@Path ('/info/:type' )
getCatInfo (
@Param ('type' ) type : string ,
@Query ('info' ) info: string ) {
return { type , info }
}
@Post
@Path ('/info/:type' )
CreateCat (
@Param ('type' ) type : string ,
@Body ('requestBody' ) requestBody: any ) {
return {
status: 200 ,
data: Object .assign(requestBody, { type }),
message: 'Created successfully...'
}
}
}
export { CatController };
这是一段TypeScript上 koa 路由类的写法,注意到在其中,使用了@Paht @Get的写法, 并且在入参中也有@PathParam('id') id: number
这样的写法。这就是装饰器。其中 @Path(‘/api’)中的API是这个装饰器的入参,在这里是注解,因为这个框架通过Reflect.defineMetadata将这个入参写入到了该方法中。
Reflect.defineMetadata是一种反射的写法,保存一个对象到缓存中,通过Reflect.getMetadata来访问缓存,无需做过多操作
思考一下该如何实现。
上面我们一共引入了六个装饰器Path, Get, Post, Param, Query, Body
, 其中path既是类装饰器又是方法装饰器,get和post是方法装饰器,且装饰在path方法装饰器之上,Param, Query, Body 是属性装饰器。考虑到装饰器的执行顺序,先是属性装饰器,再是方法装饰器,然后是类装饰器。
koa-decorate 参数装饰器 三个参数装饰器 Param, Query, Body 很简单,定义一个独一无二的Symbol
类型的键,保存被修饰的参数的值和索引。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const paramSymbolKey = Symbol.for("param" );
const querySymbolKey = Symbol.for("query" );
const bodySymbolKey = Symbol.for("body" );
const Param = argName => {
return (
target: Object ,
propertyKey: string | symbol,
argIndex: number
) => {
const args =
Reflect.getMetadata(paramSymbolKey, target, propertyKey) || {};
args[argName] = argIndex;
Reflect.defineMetadata(paramSymbolKey, args, target, propertyKey);
};
};
query 和 body 参数装饰器与 param 装饰器的内容大同小异,只不过是保存的不同的键的对象。