以前已經分別介紹了方法裝飾器、屬性裝飾器和類裝飾器,這篇文章咱們來繼續關注這些話題:segmentfault
咱們將圍繞如下這個例子,來探討這些概念:數組
class Person { public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } public saySomething(something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
TypeScript對於參數裝飾器的聲明以下app
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;
以下咱們爲類Person
的saySomething
方法的參數添加一個參數裝飾器函數
public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; }
最終被編譯爲JavaScript的樣子爲:this
Object.defineProperty(Person.prototype, "saySomething", __decorate( [__param(0, logParameter)], Person.prototype, "saySomething", Object.getOwnPropertyDescriptor(Person.prototype, "saySomething") ) ); return Person;
若是將其和以前的裝飾器比較,是否會發現又使用了Object.defineProperty()
方法,那麼是否意味着saySomething
將被__decorated
函數的返回值替換?prototype
咱們發現這裏有個新函數__param
,TypeScript編譯器生成以下:code
var __param = this.__param || function (index, decorator) { // 返回一個裝飾器函數 return function (target, key) { // 應用裝飾器(忽略返回值) decorator(target, key, index); } };
如上所示,調用參數裝飾器,其並無返回值,這就意味着,函數__decorate
的調用返回並無覆蓋方法saySomething
,也很好理解:參數裝飾器要毛返回。索引
可見參數裝飾器函數須要3個參數:被裝飾類的原型,裝飾參數所屬的方法名,參數的索引。具體的實現以下:ip
function logParameter(target: any, key : string, index : number) { var metadataKey = `log_${key}_parameters`; if (Array.isArray(target[metadataKey])) { target[metadataKey].push(index); } else { target[metadataKey] = [index]; } }
其中向類的原型中增長一個新的屬性metadataKey
,該屬性值是一個數組,包含所裝飾參數的索引,能夠把它看成元數據。get
參數裝飾器不該當用來修改構造器、方法或屬性的行爲,它只應當用來產生某種元數據。一旦元數據被建立,咱們即可以用其它的裝飾器去讀取它。
官方TypeScript裝飾器建議定義一個以下的裝飾器工廠:
裝飾器工廠首先是一個函數,它接受任意數量的參數,同時返回如前所述的四種之一特定類型的裝飾器。
雖然已經討論四種裝飾是如何實現及使用的,但仍是有一些能夠改進的地方,觀察下面的代碼片斷:
@logClass class Person { @logProperty public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @logMethod public saySomething(@logParameter something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
這裏裝飾器的使用是沒問題的,但若是咱們能夠不關心裝飾器的類型,而在任何地方使用豈不方便,就像下面的樣子:
@log class Person { @log public name: string; public surname: string; constructor(name : string, surname : string) { this.name = name; this.surname = surname; } @log public saySomething(@log something : string) : string { return this.name + " " + this.surname + " says: " + something; } }
這邊是裝飾器工廠的使用訴求,它能夠識別具體狀況下該使用哪一種類型的裝飾器,幸運的是,咱們能夠經過傳遞給裝飾器的參數來區分它的類型。
function log(...args : any[]) { switch(args.length) { case 1: return logClass.apply(this, args); case 2: return logProperty.apply(this, args); case 3: if(typeof args[2] === "number") { return logParameter.apply(this, args); } return logMethod.apply(this, args); default: throw new Error("Decorators are not valid here!"); } }