TypeScript的一個核心原則是類型檢測重點放在值的形狀(shape),這有時候被稱爲鴨子類型化(duck typing)或結構子類型化(structural subtyping)。在TypeScript中,用接口(interfaces)來命名這些類型,來定義項目內部代碼的合約以及與外部代碼的契約。typescript
理解interface如何工做,最容易的方式就是先看一個簡單例子:
數組
function printLabel(labelledObj: {label: string}) { console.log(labelledObj.label); } var myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
當調用'printLabel'時類型檢測器開始檢查,'printLabel'函數有單個參數,要求傳入的對象有一個類型爲string,名爲'label'的屬性。注意這裏傳入的對象有多個屬性,但編譯器僅檢測所須要的屬性存在並且類型匹配便可。
能夠重寫上面的例子,但此次是用接口來描述要有一個類型爲string,名爲'label'的property:
ide
interface LabelledValue { label: string; } function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label); } var myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
interface 'LabelledValue'是描述前一個例子所須要的一個名字,它仍然表示要有一個類型爲string,名爲'label'的屬性。注意沒必要明確地給將這個接口的實現傳遞給'printLabel',這個與其餘語言相似。這裏重要的只是形狀(shape)。若是傳遞給函數的對象知足列出的需求,那麼就容許傳入。
須要指出的是類型檢測器不須要這些屬性按照某種方式排序,只要接口所需的屬性存在且類型匹配便可經過檢測。
函數
並不是須要一個接口中全部的屬性(properties)。只有在特定條件下一些屬性才存在,或者並不是存在全部的屬性。當建立相似於"option bags"模式時,可選屬性很廣泛,傳遞給函數的對象只有部分屬性被賦值。學習
下面是該模式的一個例子:
this
interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): {color: string; area: number} { var newSquare = {color: "white", area: 100}; if (config.color) { newSquare.color = config.color; } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } var mySquare = createSquare({color: "black"});
有可選屬性的接口在編碼上與其餘接口相似,每一個可選屬性在屬性聲明時用一個 '?'來標記。
編碼
可選屬性的優勢是能夠描述可能存在的屬性,同時對那些未填充的屬性也會作類型檢測。例如假定傳遞給'createSquare'的屬性名稱拼寫錯誤,則會獲得下面錯誤消息:
spa
interface SquareConfig { color?: string; width?: number; } function createSquare(config: SquareConfig): {color: string; area: number} { var newSquare = {color: "white", area: 100}; if (config.color) { newSquare.color = config.collor; // Type-checker can catch the mistyped name here } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } var mySquare = createSquare({color: "black"});
接口能夠描述JavaScript對象能夠接受的各類各樣的形狀(Shape)。 除了描述帶有屬性的對象,接口還能夠描述函數類型。
爲了用接口描述函數類型,給接口一個調用標記(call signature),相似於只給出參數列表和返回值的一個函數聲明。
.net
interface SearchFunc { (source: string, subString: string): boolean; }
一旦定義,就能夠像其餘接口同樣來使用該函數類型接口。下面展現如何建立一個函數類型變量,將相同類型的一個函數值賦值給它。
翻譯
var mySearch: SearchFunc; mySearch = function(source: string, subString: string) { var result = source.search(subString); if (result == -1) { return false; } else { return true; } }
函數類型要可以經過類型檢測,不須要參數名稱保持一致。能夠將上面的例子寫爲:
var mySearch: SearchFunc; mySearch = function(src: string, sub: string) { var result = src.search(sub); if (result == -1) { return false; } else { return true; } }
函數參數被依次一個一個檢測,檢測每一個參數位置對應的類型是否匹配。並且這裏函數表達式的返回類型已經由返回值(false與true)暗示出。若是函數表達式返回的是numbers或strings,那麼類型檢測器將告警:返回類型與SearchFunc接口描述的返回類型不匹配。
相似於如何利用接口來描述函數類型,接口也能夠描述數組類型。數組類型有一個描述對象索引的'index'類型,以及訪問索引對應的返回類型。
interface StringArray { [index: number]: string; } var myArray: StringArray; myArray = ["Bob", "Fred"];
index能夠有兩種類型:string和number。能夠同時支持兩種index類型,但要求從numeric index返回的類型必須是從string index返回類型的子類型。
index標記功能的強大在於可描述數組和字典模式,還要求屬性都要匹配索引返回類型。在下面例子中,屬性沒有匹配索引返回類型,所以類型檢測器給出錯誤:
interface Dictionary { [index: string]: string; length: number; // error, the type of 'length' is not a subtype of the indexer }
在C#和Java等語言中接口最多見的一個用途是,明確強制類須要知足一個特定的契約,在TypeScript語言中一樣適用:
interface ClockInterface { currentTime: Date; } class Clock implements ClockInterface { currentTime: Date; constructor(h: number, m: number) { } }
接口中的方法也要在類中實現,就像下面例子中'setTime'方法:
interface ClockInterface { currentTime: Date; setTime(d: Date); } class Clock implements ClockInterface { currentTime: Date; setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) { } }
接口描述類的公開(Public)部分,而不包含私有部分。能夠據此來檢測類中也包含類實例私有部分的數據類型。
當使用類與接口時,要注意類有兩種類型:靜態類型部分與實例類型部分(the type of the static side and the type of the instance side)。若是建立一個有構造函數標記的接口,而後試圖建立一個實現該接口的類時將獲得錯誤:
interface ClockInterface { new (hour: number, minute: number); } class Clock implements ClockInterface { currentTime: Date; constructor(h: number, m: number) { } }
這是由於當類實現一個接口時,只檢測類的實例部分。因爲構造函數是在靜態部分,所以實例部分中沒有包含構造函數,當檢測時就報錯。
這時,須要在類中直接實現靜態部分。在下面例子中直接使用類來實現靜態部分:
interface ClockStatic { new (hour: number, minute: number); } class Clock { currentTime: Date; constructor(h: number, m: number) { } } var cs: ClockStatic = Clock; var newClock = new cs(7, 30);
與類很類似的是interfaces能夠擴展。這樣就能夠將一個接口中的成員拷貝到另外一個接口中,所以能夠將接口劃分爲更細的可重用的組件:
interface Shape { color: string; } interface Square extends Shape { sideLength: number; } var square = <Square>{}; square.color = "blue"; square.sideLength = 10;
一個接口能夠擴展多個接口,將這些接口組合在一塊兒:
interface Shape { color: string; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke { sideLength: number; } var square = <Square>{}; square.color = "blue"; square.sideLength = 10; square.penWidth = 5.0;
前面提到,接口能夠描述JavaScript中的許多類型。因爲JavaScript語言的動態和靈活性,可能遇到一個對象是上面多個類型的組合體。
在下面例子中的對象包含一個函數類型,一個對象類型,以及一些屬性:
interface Counter { (start: number): string; interval: number; reset(): void; } var c: Counter; c(10); c.reset(); c.interval = 5.0;
當與第三方JavaScript交互時,可能會用相似上面的模式來描述一個類型的完整形狀(shape)。
翻譯後記:
須要學習下 鴨子類型化(duck typing)、結構子類型化(structural subtyping)、"option bags"模式。
[1] http://www.typescriptlang.org/Handbook#interfaces
[2] TypeScript - Interfaces, 破狼blog, http://greengerong.com/blog/2014/11/13/typescript-interfaces/
[3] TypeScript系列1-簡介及版本新特性, http://my.oschina.net/1pei/blog/493012
[4] TypeScript手冊翻譯系列1-基礎類型, http://my.oschina.net/1pei/blog/493181