裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法, 訪問符,屬性或參數(shù)上,可以修改類的行為。 裝飾器使用 @expression這種形式,expression求值后必須為一個(gè)函數(shù),它會在運(yùn)行時(shí)被調(diào)用,被裝飾的聲明信息做為參數(shù)傳入。
例:
@Path('/hello')
class HelloService {}
在TypeScript中裝飾器還屬于實(shí)驗(yàn)性語法,所以要想使用必須在配置文件中tsconfig.json編譯選項(xiàng)中開啟:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
如何定義裝飾器
裝飾器本身其實(shí)就是一個(gè)函數(shù),理論上忽略參數(shù)的話,任何函數(shù)都可以當(dāng)做裝飾器使用。例:
demo.ts
function Path(target:any) {
console.log("I am decorator.")
}
@Path
class HelloService {}
使用tsc編譯后,執(zhí)行命令node demo.js,輸出結(jié)果如下:
I am decorator.
裝飾器執(zhí)行時(shí)機(jī)
修飾器對類的行為的改變,是代碼編譯時(shí)發(fā)生的(不是TypeScript編譯,而是js在執(zhí)行機(jī)中編譯階段),而不是在運(yùn)行時(shí)。這意味著,修飾器能在編譯階段運(yùn)行代碼。也就是說,修飾器本質(zhì)就是編譯時(shí)執(zhí)行的函數(shù)。
在Node.js環(huán)境中模塊一加載時(shí)就會執(zhí)行
函數(shù)柯里化解決參數(shù)問題
但是實(shí)際場景中,有時(shí)希望向裝飾器傳入一些參數(shù), 如下:
@Path("/hello", "world")
class HelloService {}
此時(shí)上面裝飾器方法就不滿足了(VSCode編譯報(bào)錯(cuò)),這是我們可以借助JavaScript中函數(shù)柯里化特性
function Path(p1: string, p2: string) {
return function (target) { // 這才是真正裝飾器
// do something
}
}
五種裝飾器
在TypeScript中裝飾器可以修飾四種語句:類,屬性,訪問器,方法以及方法參數(shù)。
1 類裝飾器
應(yīng)用于類構(gòu)造函數(shù),其參數(shù)是類的構(gòu)造函數(shù)。
注意class并不是像Java那種強(qiáng)類型語言中的類,而是JavaScript構(gòu)造函數(shù)的語法糖。
function Path(path: string) {
return function (target: Function) {
!target.prototype.$Meta && (target.prototype.$Meta = {})
target.prototype.$Meta.baseUrl = path;
};
}
@Path('/hello')
class HelloService {
constructor() {}
}
console.log(HelloService.prototype.$Meta);// 輸出:{ baseUrl: '/hello' }
let hello = new HelloService();
console.log(hello.$Meta) // 輸出:{ baseUrl: '/hello' }
2 方法裝飾器
它會被應(yīng)用到方法的 屬性描述符上,可以用來監(jiān)視,修改或者替換方法定義。
方法裝飾會在運(yùn)行時(shí)傳入下列3個(gè)參數(shù):
- 1、對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實(shí)例成員是類的原型對象。
- 2、成員的名字。
- 3、成員的屬性描述符。
function GET(url: string) {
return function (target, methodName: string, descriptor: PropertyDescriptor) {
!target.$Meta && (target.$Meta = {});
target.$Meta[methodName] = url;
}
}
class HelloService {
constructor() { }
@GET("xx")
getUser() { }
}
console.log((<any>HelloService).$Meta);
注意:在vscode編輯時(shí)有時(shí)會報(bào)作為表達(dá)式調(diào)用時(shí),無法解析方法修飾器的簽名。錯(cuò)誤,此時(shí)需要在tsconfig.json中增加target配置項(xiàng):
{
"compilerOptions": {
"target": "es6",
"experimentalDecorators": true,
}
}
3 方法參數(shù)裝飾器
參數(shù)裝飾器表達(dá)式會在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,傳入下列3個(gè)參數(shù):
- 1、對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實(shí)例成員是類的原型對象。
- 2、參數(shù)的名字。
- 3、參數(shù)在函數(shù)參數(shù)列表中的索引。
function PathParam(paramName: string) {
return function (target, methodName: string, paramIndex: number) {
!target.$Meta && (target.$Meta = {});
target.$Meta[paramIndex] = paramName;
}
}
class HelloService {
constructor() { }
getUser( @PathParam("userId") userId: string) { }
}
console.log((<any>HelloService).prototype.$Meta); // {'0':'userId'}
4 屬性裝飾器
屬性裝飾器表達(dá)式會在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用,傳入下列2個(gè)參數(shù):
- 1、對于靜態(tài)成員來說是類的構(gòu)造函數(shù),對于實(shí)例成員是類的原型對象。
- 2、成員的名字。
function DefaultValue(value: string) {
return function (target: any, propertyName: string) {
target[propertyName] = value;
}
}
class Hello {
@DefaultValue("world") greeting: string;
}
console.log(new Hello().greeting);// 輸出: world
裝飾器加載順序
function ClassDecorator() {
return function (target) {
console.log("I am class decorator");
}
}
function MethodDecorator() {
return function (target, methodName: string, descriptor: PropertyDescriptor) {
console.log("I am method decorator");
}
}
function Param1Decorator() {
return function (target, methodName: string, paramIndex: number) {
console.log("I am parameter1 decorator");
}
}
function Param2Decorator() {
return function (target, methodName: string, paramIndex: number) {
console.log("I am parameter2 decorator");
}
}
function PropertyDecorator() {
return function (target, propertyName: string) {
console.log("I am property decorator");
}
}
@ClassDecorator()
class Hello {
@PropertyDecorator()
greeting: string;
@MethodDecorator()
greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
}
輸出結(jié)果:
I am property decorator
I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am class decorator
從上述例子得出如下結(jié)論:
1、有多個(gè)參數(shù)裝飾器時(shí):從最后一個(gè)參數(shù)依次向前執(zhí)行
2、方法和方法參數(shù)中參數(shù)裝飾器先執(zhí)行。
3、類裝飾器總是最后執(zhí)行。
4、方法和屬性裝飾器,誰在前面誰先執(zhí)行。因?yàn)閰?shù)屬于方法一部分,所以參數(shù)會一直緊緊挨著方法執(zhí)行。上述例子中屬性和方法調(diào)換位置,輸出如下結(jié)果:
I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am property decorator
I am class decorator