從C#到TypeScript - 裝飾器
在C#裏面如果想要不直接修改類或方法,但給類或方法添加一些額外的信息或功能,可以想到用Attribute
,這是一個十分方便的功能裝飾器。
用TypeScript同樣也可以利用裝飾器來給類、函數、屬性以及參數添加附加功能,裝飾器是ES7的一個提案,在TypeScript裏已經有實現可用,不過需要在tsconfig.json
裏啓用experimentalDecorators
。
"compilerOptions": { ..., // other options "experimentalDecorators": true}
裝飾器介紹
TypeScript中裝飾器可以應用到類、方法、屬性及函數參數上,而且可以同時應用多個。
裝飾器的寫法是@name()
,()
可以不要,也可以在裏面寫一些參數。
@Testable @Log('controller')class Controller{ @GET getContent(@QueryParam arg: string): string{ return ''; } }
裝飾器的實現
裝飾器根據實現可以分兩種:
一種是不帶括號,和屬性一樣,如@Testable
。
function Testable(target: Function) { // 類、方法、屬性、方法參數的參數各不相同 //這裏可以記錄一些信息到target,或者針對target做一些處理,如seal}
另外一種是帶括號的,和函數一樣,如@Log('controller')
,實現函數裏的參數就是括號裏的參數,而且需要返回一個function
。
function Log(name: string) { // name就是傳進來的參數'controller' return function(target: Function) { // 類、方法、屬性、方法參數的參數各不相同 // 這裏可以根據name和target來做一些處理 } }
類裝飾器
上面的(target: Function)
其實就是類的裝飾器參數,指向的是類的構造函數,如果想給類加一個簡單的seal功能,可以這樣做:
function sealed(target: Function) { Object.seal(target); Object.seal(target.prototype); } @sealedclass Test{ } Test.prototype.test = ''; // 運行時出錯,不能添加
上面的sealed
就是類的裝飾器,target
指構造函數,類裝飾器就這麼一個參數。
方法裝飾器
方法裝飾器的使用方法和類裝飾器類似,只是參數不一樣,方法裝飾器有三個參數:
如果裝飾的是靜態方法,則是類的構造函數,如果是實例方法則是類的原型。
方法的名字。
方法的
PropertyDescriptor
。PropertyDescriptor
即屬性描述符,有
configurable 是否可以配置,如動態添加刪除函數屬性之類
writable 是否可寫,可以用來設置只讀屬性
enumerable 是否可枚舉,即是否能在for...in
中能枚舉到
value 對象或屬性的值
有了這些參數就可以很好的給方法添加一些功能,比如下面實現類型WebApi裏的Get的路由:
const Router = Symbol(); // 唯一key,用來存裝飾器的信息function GET(path?: string) { // GET帶了個可選參數 return (target: any, name: string) => setMethodDecorator(target, name, 'GET', path); }//把method和path存起來,路由查找的時候就可以用了function setMethodDecorator(target: any, name: string, method: string, path?: string){ target[Router] = target[Router] || {}; target[Router][name] = target[Router][name] || {}; target[Router][name].method = method; target[Router][name].path = path; }// 通過PropertyDescriptor來設置enumerablefunction Enumerable(enumerable: boolean) { return (target: any, name: string, descriptor: PropertyDescriptor) => { descriptor.enumerable = enumerable; }; }class Controller{ @GET @Enumerable(true) getContent(arg: string): string{ return ''; } }
參數裝飾器
方法參數同樣可以有裝飾器,同樣有三個參數,前兩個參數和方法的一致,最後一個參數是所裝飾的參數的位置。
能過參數裝飾器可以給方法動態的檢查或設置參數值,下面是檢查參數是否爲空,爲空則拋出異常。
const CheckNullKey = Symbol();const Router = Symbol();// 把CheckNull裝飾的參數存起來function CheckNull(target: any, name: string, index: number) { target[Router] = target[Router] || {}; target[Router][name] = target[Router][name] || {}; target[Router][name].params = target[Router][name].params || []; target[Router][name].params[index] = CheckNullKey; }// 找出CheckNull的參數,並檢查參數值,爲空則拋異常,否則繼續執行方法function Check(target: any, name: string, descriptor: PropertyDescriptor) { let method = descriptor.value; descriptor.value = function () { let params = target[Router][name].params; if (params) { for (let index = 0; index < params.length; index++) { if (params[index] == CheckNullKey && // 找到CheckNull的參數並拋異常 (arguments[index] === undefined || arguments[index] === null)) { throw new Error("Missing required argument."); } } } return method.apply(this, arguments); } }class Controller{ @Check getContent(@CheckNull id: string): string{ console.info(id); return id; } }new Controller().getContent(null); // error : Missing required argument.
屬性裝飾器
用法同上,參數只有兩個,和類裝飾器的前兩個一樣,常用來標識屬性的特性。
function Column(target: any, name: string) { //把name存起來,這個column僅僅是標識出來對應數據庫中的列,常用在ORM框架中}class Table{ @Column name: string; }
另外還有屬性訪問器的裝飾器,和方法基本一樣,同樣的三個參數,不過同個屬性的get
和set
只能有一個有,而且必須是先聲明的那個。
class User { private _name: string; @Enumerable(true) get name(){ return this._name; } set name(value: string) { this._name = value; } }
多個裝飾器的執行順序
一個聲明可以添加多個裝飾器,所以會有個執行先後順序。
首先從上到下執行裝飾器函數,然後再從下往上應用帶括號的裝飾器返回的函數。
function Test1(){ console.info('eval test1'); return function(target: any, name: string, descriptor: PropertyDescriptor){ console.info('apply test1'); } }function Test2(){ console.info('eval test2'); return function(target: any, name: string, descriptor: PropertyDescriptor){ console.info('apply test2'); } }class User1{ @test1() @Test2() getName(){ } }
結果是:
eval test1eval test2 apply test2 apply test1
總之,裝飾器等於引入了天然的裝飾模式,給類,方法等添加額外功能。不過裝飾器目前還不算太穩定,但是由於確實方便,已經有成熟項目在使用了。