TypeScript 裝飾器
1..裝飾器的概念
· 裝飾器是一種特殊型別的宣告,它能夠被附加到類的宣告,方法,訪問符,屬性或引數上;
· 裝飾器使用 @expression 這種形式,expression求值後必須為一個函式;
· 它會在執行時被呼叫,被裝飾的宣告資訊做為引數傳入。
· 裝飾器執行時機是在程式碼編譯時發生的(不是 TypeScript 編譯,而是在 JavaScript 執行編譯階段)
啟用裝飾器特性,必須在命令列或 tsconfig.json 裡啟用 experimentalDecorators 編譯器選項:
2.裝飾器語法
啟用裝飾器特性,必須在命令列或 tsconfig.json 裡啟用 experimentalDecorators 編譯器選項:
// 在命令列中使用 tsc--target ES5--experimentalDecorators // tsconfig.json 中開啟: { "compilerOptions": { "target": "ES5", "experimentalDecorators": true } }
裝飾器定義與應用舉例:
// 裝飾器定義 function sealed(target) { // do something with "target" ... } // 或使用裝飾器工廠函式 function color(value: string) { // 這是一個裝飾器工廠 return function (target) { // 這是裝飾器 // do something with "target" and "value"... } } // 應用到一個宣告上,例如: @sealed @color let x: string; // 或 @sealed @color let x: string
在 TypeScript 裡,當多個裝飾器應用在一個宣告上時會進行如下步驟的操作:
· 由上至下依次對裝飾器表示式求值。
· 求值的結果會被當作函式,由下至上依次呼叫。
多個裝飾器應用在一個宣告,舉例:
// 多個裝飾器應用在一個宣告 function f() { console.log("f(): evaluated"); return function (target, propertyKey: string) { console.log("f(): called"); } } function g() { console.log("g(): evaluated"); return function (target, propertyKey: string) { console.log("g(): called"); } } class C { @f() @g() method() { } } // f(): evaluated // g(): evaluated // g(): called // f(): called
3.類裝飾器
類中不同宣告上的裝飾器將按以下規定的順序應用:
· 有多個引數裝飾器時:從最後一個引數依次向前執行。
· 方法和方法引數中,引數裝飾器先執行。
· 方法和屬性裝飾器,誰在前面誰先執行。因為引數屬於方法一部分,所以引數會挨著方法執行。
·類裝飾器總是最後執行。
類裝飾器在類宣告之前被宣告(緊靠著類宣告)。
類裝飾器應用於類建構函式,可以用來監視,修改或替換類定義。
// 類裝飾符例項 function Path(path: string) { return function (target: Function) { !target.prototype.$Meta && (target.prototype.$Meta = {}) target.prototype.$Meta.baseUrl = path; }; } @Path('/hello') class HelloService { [x: string]: any; constructor() { } } console.log(HelloService.prototype.$Meta); // => { baseUrl: '/hello' } let hello = new HelloService(); console.log(hello.$Meta) // => { baseUrl: '/hello' }
4.方法裝飾器
方法裝飾器宣告在一個方法的宣告之前,可以用來監視,修改或者替換方法定義。
方法裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:
· 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件;
· 成員的名字;
· 成員的屬性描述符;
注意:如果程式碼輸出目標版本小於ES5,屬性描述符將會是 undefined,返回值會被忽略。 如果方法裝飾器返回一個值,它會被用作方法的屬性描述符。
方法裝飾器,舉例:
// 方法裝飾符例項 function GET(alias: string) { return function (target, methodName: string, descriptor: PropertyDescriptor) { target._alias = alias; } } class HelloService { _alias: string; constructor() { } @GET("getMyName") getUser() { } } let hello = new HelloService(); console.log(hello._alias); // => gtMyName
5.訪問裝飾器
訪問器裝飾器宣告在一個訪問器的宣告之前,可以用來監視,修改或替換一個訪問器的定義。
訪問裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:
· 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件;
· 成員的名字;
· 成員的屬性描述符;
注意:如果程式碼輸出目標版本小於ES5,屬性描述符將會是 undefined,返回值會被忽略。
如果訪問裝飾器返回一個值,它會被用作方法的屬性描述符。
TypeScript 不允許同時裝飾一個成員的get和set訪問器;
一個成員的所有裝飾的必須應用在文件順序的第一個訪問器上;因為在裝飾器應用於一個屬性描述 符時,它聯 合了get和set訪問器,而不是分開宣告的。
訪問裝飾器,舉例:
// 訪問裝飾器例項 function access(value: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { !target.$Meta && (target.$Meta = {}) target.$Meta[propertyKey] = `It's a ${value} method` }; } class Point { private _x: number; private _y: number; constructor(x: number, y: number) { this._x = x; this._y = y; } @access('get') get x() { return this._x; } @access('set') set y(y: number) { this._y = y; } } let point = new Point(1, 2); console.log(point['$Meta']); // => { x: "It's a get method", y: "It's a set method" }
6..屬性裝飾器
屬性裝飾器宣告在一個屬性宣告之前。
屬性裝飾器表示式會在執行時當作函式被呼叫,傳入下列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
7.引數裝飾器
引數裝飾器宣告在一個引數宣告之前, 引數裝飾器應用於類建構函式或方法宣告。
引數裝飾器表示式會在執行時當作函式被呼叫,傳入下列3個引數:
· 對於靜態成員來說是類的建構函式,對於例項成員是類的原型物件;
· 成員的名字;
· 引數在函式引數列表中的索引;
注意:
引數裝飾器只能用來監視一個方法的引數是否被傳入。
引數裝飾器的返回值會被忽略。
引數裝飾器,舉例:
// 引數裝飾器例項 function PathParam(paramName: string) { return function (target, methodName: string, paramIndex: number) { !target.$Meta && (target.$Meta = {}); target.$Meta[paramIndex] = paramName; } } class HelloService { constructor() { } getUser( @PathParam("the user's id") userId: string ) { } } console.log((<any>HelloService).prototype.$Meta); // => { '0': "the user's id" }