裝飾器
裝飾器是一種特殊類型的聲明,它能夠被附加到類聲明,方法,訪問符,屬性或參數上。 裝飾器使用@expression
這種形式,expression必須是一個函數,它會在運行時被調用,被裝飾的聲明信息做為參數傳入。
Typescript中的裝飾器是一項實驗性功能,需要在tsconfig.json中開啟該特性
{ "compilerOptions": { "experimentalDecorators": true } }
例如,有一個@sealed
裝飾器,我們這樣定義sealed
:
function sealed(target: any) { // 操作被裝飾對象 }
裝飾器工廠
如果需要給裝飾器添加一些動態行為,比如開發一個監控統計的裝飾器,需要傳入當前統計的事件名稱,有多個事件名稱時只需要變更傳入的事件名而不用重復定義裝飾器。
這時候需要使用到裝飾器工廠。裝飾器工廠也是一個函數,只不過它的返回值是一個裝飾器。例如如下的事件監控裝飾器:
function event(eventName: string) { return function(target: any) { // 獲取到當前eventName和被裝飾對象進行操作 } }
裝飾器組合
多個裝飾器可以同時應用到被裝飾對象上,例如下面的例子:
@sealed @test('test') class Demo { }
裝飾器執行順序:
- 裝飾器工廠需要先求值,再裝飾,求值順序是由上到下
- 裝飾器可以直接求值,裝飾順序是由下到上
上面的說明可以難以理解,下面舉一個實際的例子:
function f() { console.log('f求值'); return function(target: any) { console.log('f裝飾'); } } function g() { console.log('g求值'); return function(target: any) { console.log('g裝飾'); } } @f() @g() class Demo { }
上例的執行順序為
f求值
g求值
g裝飾
f裝飾
因為先求值,所以在上面的f會比g先求值。因為裝飾器是由下到上裝飾,所以求值后的g比f先執行。
裝飾器類型
根據被裝飾的對象不同,裝飾器分為以下幾類:
- 類裝飾器
- 方法裝飾器
- 屬性裝飾器
- 函數參數裝飾器
類裝飾器
類裝飾器在定義類的地方。類裝飾器可以監視、修改或替換類定義。類的構造函數將作為唯一參數傳遞給裝飾器。如果類裝飾器返回一個值,它會使用返回的構造函數替換原來的類聲明。
function sealed(target: Function) { Object.seal(target); Object.seal(target.prototype); } @sealed class Demo {}
下面來一個替換構造函數的示例:
function replace<T extends {new(...args: any[]):{}}>(target: T) { return class extends target { newname = "newName"; age = 18 } } @replace class Demo { oldname = "oldname"; constructor(oldname: string) { this.oldname = oldname; } } console.log(new Demo("oldname"));
以上例程會輸出
class_1 { oldname: 'oldname', newname: 'newName', age: 18 }
可以看到通過裝飾器新增的newname和age屬性已經成功注入了。
方法裝飾器
方法裝飾器用來裝飾類的方法(靜態方法和實例方法都可以)。方法裝飾器可以監視、修改或替換方法定義。
方法裝飾器接收3個參數:
- 類的原型對象,如果是靜態方法則為類的構造函數
- 方法名稱
- 方法的屬性描述符
下面是一個修改
方法行為的裝飾器:
function hack(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const oldFunction = target[propertyKey]; // 獲取方法引用 const newFunction = function(...args: any[]) { console.log('call function ', propertyKey); oldFunction.call(target, ...args); } descriptor.value = newFunction; // 替換原聲明 } class Demo { @hack demo() { console.log('call demo'); } } const demo = new Demo(); demo.demo();
以上例程輸出如下:
call function demo call demo
屬性裝飾器
屬性裝飾器用來裝飾類的成員屬性。屬性裝飾器接收兩個參數:
- 類的原型對象,如果是靜態方法則為類的構造函數
- 屬性名
function demo(value: string) { return function(target: any, propertyKey: string) { target[propertyKey] = value; } } class Demo { @demo('haha') name?: string; } const d = new Demo(); console.log(d.name);
屬性裝飾器多用在屬性依賴注入上面
函數參數裝飾器
參數裝飾器表達式會在運行時當作函數被調用,傳入下列3個參數:
- 對於靜態成員來說是類的構造函數,對於實例成員是類的原型對象。
- 參數的名字。
- 參數在函數參數列表中的索引。
function PathParam(paramDesc: string) { return function (target: any, paramName: string, paramIndex: number) { !target.$meta && (target.$meta = {}); target.$meta[paramIndex] = paramDesc; } } class Demo { constructor() { } getUser( @PathParam("userId") userId: string) { } } console.log((<any>Demo).prototype.$meta);
以上例程輸出
{ '0': 'userId' }
函數參數裝飾器可以用在開發Web框架時自動注入請求參數。
結語
裝飾器的介紹到這里就暫時結束了,裝飾器的存在讓Typescript有了與Java和C#等語言的注解相同的功能。當然,基於裝飾器能做的工作是相當多的,注明的Angular2就大量使用了裝飾器來分離業務邏輯。