文章標題讓人霧煞煞,不過也想不出更精準的形容,但看下去大家(我是指有選修TypeScript的同學,其餘同學請前往自己的選課教室)應該就明白了…
故事是-我在寫TypeScript單元測試,想做一個假物件模擬SingalR HubProxy的行為,由NuGet取得定義檔signalr.d.ts,其中HubProxy介面定義如下:
interface HubProxy {
(connection: HubConnection, hubName: string): HubProxy;
state: any;
connection: HubConnection;
hubName: string;
init(connection: HubConnection, hubName: string): void;
hasSubscriptions(): boolean;
on(eventName: string, callback: (...msg: any[]) => void ): HubProxy;
off(eventName: string, callback: (msg: any) => void ): HubProxy;
invoke(methodName: string, ...args: any[]): JQueryDeferred<any>;
}
直覺想到的寫法是宣告一個新類別實做HubProxy介面,class fakeHubProxy implements HubProxy,加上state、connection、hubName屬性,init()、hasSubscriptions()、on()、off()、invoke()等方法,這些都不成問題,但在第一條宣告上我觸礁了。
(connection: HubConnection, hubName: string): HubProxy 並非建構式,而是指傳入connection及hubName,將HubProxy當成函式(Function)呼叫(不使用new關鍵字,術語為Callable Signature),該函式需傳回符合HubProxy介面的物件,例如: var hub = HubProxy(conn, "hubName");。
爬文後,得到結論:不可能!TypeScript類別不支援此功能,需改用函式。
在這串stackoverflow討論中,我認同basarat的看法:
The reason is that an interface is primarily designed to describe anything that JavaScript objects can do. Therefore it needs to be really robust. A TypeScript class however is designed to represent specifically the prototype inheritance in a more OO conventional / easy to understand / easy to type way.
在TypeScript裡,「介面」被設計成具有很高的彈性,以包容任何JavaScript物件,再複雜都能用介面定義規範。而「類別」不同,它試圖以傳統OO嚴謹易懂的方式包裝JavaScript獨特的Prototype繼承概念。
結論是當interface出現(param, param): resultType形式的宣告,只能用函式滿足該interface,不可能用類別實做。以下是一個應用範例:Demo
interface Foo {
(name: string): Foo;
/** 名稱 */
Name: string;
}
//使用函式實現interface Foo
var foo1: Foo = <Foo>function(name: string) {
//小技巧:將self宣告為Foo型別,以享用Intellisense
var self: Foo = this;
self.Name = name;
return self;
}
var test: Foo = foo1("Jeffrey");
alert(test.Name);
兩點補充:
- function(name: string) { … }寫法與一般函式無異,透過<Foo>語法強制轉型即可指定給Foo型別變數。延伸閱讀:參見新手村心得第5點
- 在函式內部使用this.Name就能增加新屬性,但我用了一個小技巧「另外宣告var self: Foo = this」,以self代替this能享受強型別的好處;若要宣告Foo介面未定義的屬性方法,則可改用this(型別為any),魚與熊掌兼得~