在咱們深刻了解 Angular 2 中 @NgModule、@Component、@Injectable 等常見的裝飾器以前,咱們要先了解 TypeScript 中的裝飾器。裝飾器是一個很是酷的特性,最先出如今 Google 的 AtScript 中,它出現的目的是爲了讓開發者,開發出更容易維護、更容易理解的 Angular 代碼。使人興奮的是,在2015年 Angular 團隊跟 MicroSoft 的 TypeScript 團隊通過數月的的交流,最終決定採用 TypeScript 來重寫 Angular 2 項目 。app
它是一個表達式函數
該表達式被執行後,返回一個函數ui
函數的入參分別爲 targe、name 和 descriptorthis
執行該函數後,可能返回 descriptor 對象,用於配置 target 對象 prototype
類裝飾器 (Class decorators)code
屬性裝飾器 (Property decorators)對象
方法裝飾器 (Method decorators)索引
參數裝飾器 (Parameter decorators)ip
類裝飾器聲明:開發
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void
類裝飾器顧名思義,就是用來裝飾類的。它接收一個參數:
target: TFunction - 被裝飾的類
看完第一眼後,是否是感受都很差了。沒事,咱們立刻來個例子:
function Greeter(target: Function): void { target.prototype.greet = function (): void { console.log('Hello!'); } } @Greeter class Greeting { constructor() { // 內部實現 } } let myGreeting = new Greeting(); myGreeting.greet(); // console output: 'Hello!';
上面的例子中,咱們定義了 Greeter 類裝飾器,同時咱們使用了 @Greeter 新的語法,來使用裝飾器。
(備註:讀者能夠直接複製上面的代碼,在 TypeScript Playground 中運行查看結果)。
有的讀者可能想問,例子中老是輸出 Hello! ,能自定義輸出的問候語麼 ?這個問題很好,答案是能夠的。具體實現以下:
function Greeter(greeting: string) { return function(target: Function) { target.prototype.greet = function(): void { console.log(greeting); } } } @Greeter('您好') class Greeting { constructor() { // 內部實現 } } let myGreeting = new Greeting(); myGreeting.greet(); // console output: '您好!';
屬性裝飾器聲明:
declare type PropertyDecorator = (target:Object, propertyKey: string | symbol ) => void;
屬性裝飾器顧名思義,用來裝飾類的屬性。它接收兩個參數:
target: Object - 被裝飾的類
propertyKey:string | symbol - 被裝飾類的屬性名
趁熱打鐵,立刻來個例子熱熱身:
function LogChanges(target: Object, key: string) { var propertyValue: string = this[key]; if(delete this[key]) { Object.defineProperty(target, key, { get: function () { return propertyValue; }, set: function(newValue) { propertyValue = newValue; console.log(`${key} is now ${propertyValue}`); } }); } } class Fruit { @LogChanges name: string; } let fruit = new Fruit(); fruit.name = 'apple'; // console output: 'name is now apple' fruit.name = 'banana'; // console output: 'name is now banana'
那麼問題來了,若是用戶想在屬性變化的時候,自動刷新頁面,而不是簡單地在控制檯輸出消息,那要怎麼辦?咱們能不能參照類裝飾器自定義問候語的方式,來實現監測屬性變化的功能。具體實現以下:
function LogChanges(callbackObject: any) { return function(target: Object, key: string): void { var propertyValue: string = this[key]; if(delete this[key]) { Object.defineProperty(target, key, { get: function () { return propertyValue; }, set: function(newValue) { propertyValue = newValue; callbackObject.onchange.call(this, propertyValue); } }); } } } class Fruit { @LogChanges({ onchange: function(newValue: string): void { console.log(`The fruit is ${newValue} now`); } }) name: string; } let fruit = new Fruit(); fruit.name = 'apple'; // console output: 'The fruit is apple now' fruit.name = 'banana'; // console output: 'The fruit is banana now'
方法裝飾器聲明:
declare type MethodDecorator = <T>(target:Object, propertyKey: string | symbol, descriptor: TypePropertyDescript<T>) => TypedPropertyDescriptor<T> | void;
方法裝飾器顧名思義,用來裝飾類的屬性。它接收三個參數:
target: Object - 被裝飾的類
propertyKey: string | symbol - 方法名
descriptor: TypePropertyDescript - 屬性描述符
廢話很少說,直接上例子:
function LogOutput(tarage: Function, key: string, descriptor: any) { var originalMethod = descriptor.value; var newMethod = function(...args: any[]): any { var result: any = originalMethod.apply(this, args); if(!this.loggedOutput) { this.loggedOutput = new Array<any>(); } this.loggedOutput.push({ method: key, parameters: args, output: result, timestamp: new Date() }); return result; }; descriptor.value = newMethod; } class Calculator { @LogOutput double (num: number): number { return num * 2; } } let calc = new Calculator(); calc.double(11); // console ouput: [{method: "double", output: 22, ...}] console.log(calc.loggedOutput);
最後咱們來看一下參數裝飾器:
參數裝飾器聲明:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number ) => void
參數裝飾器顧名思義,是用來裝飾函數參數,它接收三個參數:
target: Object - 被裝飾的類
propertyKey: string | symbol - 方法名
parameterIndex: number - 方法中參數的索引值
function Log(target: Function, key: string, parameterIndex: number) { var functionLogged = key || target.prototype.constructor.name; console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has been decorated`); } class Greeter { greeting: string; constructor(@Log phrase: string) { this.greeting = phrase; } } // console output: The parameter in position 0 at Greeter has // been decorated
1.Object.defineProperty() 方法有什麼用 ?
Object.defineProperty 用於在一個對象上定義一個新的屬性或者修改一個已存在的屬性,並返回這個對象。 方法的簽名:Object.defineProperty(obj, prop, descriptor) ,參數說明以下:
obj 須要定義的屬性對象
prop 需被定義或修改的屬性名
descriptor 需被定義或修改的屬性的描述符
對象裏目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。數據描述符是一個擁有可寫或不可寫值的屬性。存取描述符是由一對 getter-setter 函數功能來描述的屬性。描述符必須是兩種形式之一,不能同時是二者。
數據描述符和存取描述符均具備如下可選鍵值:
configurable
當且僅當該屬性的 configurable 爲 true 時,該屬性纔可以被改變,也可以被刪除。默認爲 false。
enumerable
當且僅當該屬性的 enumerable 爲 true 時,該屬性纔可以出如今對象的枚舉屬性中。默認爲 false。
數據描述符同時具備如下可選鍵值:
value
該屬性對應的值。能夠是任何有效的 JavaScript 值(數值,對象,函數等)。默認爲 undefined。
writable
當且僅當僅當該屬性的writable爲 true 時,該屬性才能被賦值運算符改變。默認爲 false。
存取描述符同時具備如下可選鍵值:
get
一個給屬性提供 getter 的方法,若是沒有 getter 則爲 undefined。該方法返回值被用做屬性值。默認爲undefined。
set
一個給屬性提供 setter 的方法,若是沒有 setter 則爲 undefined。該方法將接受惟一參數,並將該參數的新值分配給該屬性。默認爲undefined。
使用示例:
var o = {}; // 建立一個新對象 Object.defineProperty(o, "a", {value : 37, writable : true, enumerable : true, configurable : true});
本文主要介紹了 TypeScript 中的四種裝飾器,瞭解裝飾器的基本分類和實現原理,爲咱們下一篇深刻 Angular 2 的 @NgModule、@Component、@Injectable 等經常使用裝飾器作好鋪墊。