Interfaces
作為TypeScript中的核心特色之一,能夠讓類型檢查幫助我們知道一個對象應該有什么,相比我們在編寫JavaScript的時候經常遇到函數需要傳遞參數,可能在編寫的時候知道這個對象能夠提供哪些值,但是以后維護的時候負責看這段代碼的人都無法確認這個對象還有其他的哪些值,就需要翻閱源碼看看調用這個函數的代碼。
第一個接口
在開始正題之前我們先來一個簡單的例子。
--TypeScript
1 function printLabel(labelObject: { label: string }) { 2 console.log(labelObject.label); 3 } 4 5 var myobj = { size: 10, label: "Size 10 Object" }; 6 printLabel(myobj);
--JavaScript
function printLabel(labelObject) { console.log(labelObject.label); } var myobj = { size: 10, label: "Size 10 Object" }; printLabel(myobj);
類型檢查會在我們調用printLabel的時候檢查傳進去的參數,確保該參數中存在一個名字為label並且類型為string的屬性,當然我們可以看到這個參數的值遠遠比我們函數要求的多,但這並不會造成影響,通過最終的源碼我們也可以看到最終生成的js代碼並沒有比原來多出多少,但是卻能夠為我們提供強大的類型檢查。
下面我們可以引入正題了,我們將使用接口的方式實現上面的例子。
--TypeScript
1 interface LabelledValue { 2 label: string 3 } 4 5 function printLabel(labelObject: LabelledValue) { 6 console.log(labelObject.label); 7 } 8 9 var myobj = { size: 10, label: "Size 10 Object" }; 10 printLabel(myobj);
--JavaScript
同上
這里我們看到如果利用接口能夠更便於管理,更主要的是接口中除了可以定義屬性也可以定義函數等,對於中大型項目,特別是前后端分離的網站來說對於以后的維護和迭代能顧節省時間成本並提高質量。
可選屬性
但是由於歷史原因,TypeScript並不能孤立存在,還是需要兼容其他庫,那么就導致我們的接口還要考慮另一種情況就是可選值,比如下面這個例子。
--TypeScript
1 interface SquareConfig { 2 color?: string; 3 width?: number; 4 } 5 6 function createSquare(config: SquareConfig): { color: string; area: number } { 7 var newsquare = { color: "white", area: 100 }; 8 if (config.color) { 9 newsquare.color = config.color; 10 } 11 if (config.width) { 12 newsquare.area = config.width * config.width; 13 } 14 return newsquare; 15 } 16 17 var mySquare = createSquare({ color: "black" });
--JavaScript
1 function createSquare(config) { 2 var newsquare = { color: "white", area: 100 }; 3 if (config.color) { 4 newsquare.color = config.color; 5 } 6 if (config.width) { 7 newsquare.area = config.width * config.width; 8 } 9 return newsquare; 10 } 11 12 var mySquare = createSquare({ color: "black" });
通過接口我們知道可選屬性就是在屬性名稱的后面加上問號就可以了,但是開發的時候要注意就是要通過if判斷下該值是否存在。
函數類型
玩轉了屬性,下面我們開始在接口中放入函數,下面我們先放一個函數。
--TypeScript
1 interface SearchFunc { 2 (source: string, substring: string): boolean; 3 } 4 5 var mySearch: SearchFunc; 6 mySearch = function (source: string, substring: string) { 7 var result = source.search(substring); 8 if (result == -1) { 9 return false; 10 } else { 11 return true; 12 } 13 };
--JavaScript
var mySearch; mySearch = function (source, substring) { var result = source.search(substring); if (result == -1) { return false; } else { return true; } };
大家肯定會很奇怪,下面為什么定義了這個接口的變量但是賦的確是一個函數,如果大家有C#和java語言的基礎會發現SearchFunc中的函數是沒有函數名的,所以mySearch的類型就是一個函數,只是會進行類型檢查,你是不能賦其他函數簽名不一樣的函數給他的。
數組類型
接口除了可以描述函數類型也可以描述數組類型,數組類型擁有一個“index”類型,是用來索引數組的,利用這個我們就可以實現除了數組以外還能夠實現字典類型。
--TypeScript
1 interface StringArray { 2 [index: number]: string; 3 } 4 5 var myArray: StringArray; 6 myArray = ["Bob", "Fred"];
--JavaScript
1 var myArray; 2 myArray = ["Bob", "Fred"];
Index類型能夠支持兩種類型:string和number,所以我們能夠實現字典類型,比如下面這種類型。
--TypeScript
1 interface StringArray { 2 [index: string]: string; 3 } 4 5 var myArray: StringArray; 6 myArray = { 7 "dede": "dede", 8 "ete":"dede" 9 };
--JavaScript
1 var myArray; 2 myArray = { 3 "dede": "dede", 4 "ete": "dede" 5 };
類類型
像C#和Java語言中一樣,接口最基礎的作用就是讓類去實現接口,所以這也是TypeScript語言的特點之一。比如下面的例子我們將實現一個帶有一個屬性的接口。
--TypeScript
1 interface ClockInterface { 2 currentTime: Date 3 } 4 5 class Clock implements ClockInterface { 6 currentTime: Date; 7 constructor(h: number, m: number) { } 8 }
--JavaScript
1 var Clock = (function () { 2 function Clock(h, m) { 3 } 4 return Clock; 5 })();
這里我們可以看到最終的JS中並沒有將currentTime作為變量加入到this中,因為在這個類中我們並沒有使用到這個值,所以這個變量只會在我們正式的使用的時候添加到這個類中,如果不用這個類就等同於沒有這個變量。
上面我們僅僅只是在接口中寫了一個屬性,下面我們在接口中增加一個方法。
--TypeScript
1 interface ClockInterface { 2 currentTime: Date; 3 setTime(d: Date); 4 } 5 6 class Clock implements ClockInterface { 7 currentTime: Date; 8 setTime(d: Date) { 9 this.currentTime = d; 10 } 11 constructor(h: number, m: number) { } 12 }
--JavaScript
1 var Clock = (function () { 2 function Clock(h, m) { 3 } 4 Clock.prototype.setTime = function (d) { 5 this.currentTime = d; 6 }; 7 return Clock; 8 })();
靜態類和實例類的區別
當我們使用類和接口,需要知道類是存在靜態和實例的,這也就意味着如果你的接口如果存在構造方法並且需要一個類去實現,那么你將會看到錯誤信息,比如下面這段。
--TypeScript
1 interface ClockInterface { 2 new (hour: number, minute: number); 3 } 4 5 class Clock implements ClockInterface { 6 currentTime: Date; 7 constructor(h: number, m: number) { } 8 }
這是因為當一個類實現一個接口的時候只有實例部分是被允許的,而構造方法恰恰屬於靜態,並不包含在內。
當然含有構造方法的接口是有其用途的,比如下面這樣的用法。
--TypeScript
1 interface ClockInterface { 2 new (hour: number, minute: number); 3 } 4 5 class Clock { 6 currentTime: Date; 7 constructor(h: number, m: number) { } 8 } 9 10 var cs: ClockInterface = Clock; 11 var newClock = new cs(2, 3);
--JavaScript
1 var Clock = (function () { 2 function Clock(h, m) { 3 } 4 return Clock; 5 })(); 6 7 var cs = Clock; 8 var newClock = new cs(2, 3);
擴展接口
這個特性跟類可以繼承其他類一樣,接口也可以擴展其他的接口,這將會導致被繼承的接口中的所有的內容都會被復制到另一個接口中。下面我們來看一個簡單的例子。
--TypeScript
1 interface Shape { 2 color: string; 3 } 4 5 interface Square extends Shape { 6 sideLength: number; 7 } 8 9 var square = <Square>{}; 10 square.color = "blue"; 11 square.sideLength = 10;
--JavaScript
1 var square = {}; 2 square.color = "blue"; 3 square.sideLength = 10;
一個接口不僅僅只能擴展一個接口,是可以擴展多個接口的。比如下面這樣。
--TypeScript
1 interface Shape { 2 color: string; 3 } 4 5 interface PenStroke { 6 penWidth: number; 7 } 8 9 interface Square extends Shape, PenStroke { 10 sideLength: number; 11 } 12 13 var square = <Square>{}; 14 square.color = "blue"; 15 square.sideLength = 10; 16 square.penWidth = 5.0;
--JavaScript
1 var square = {}; 2 square.color = "blue"; 3 square.sideLength = 10; 4 square.penWidth = 5.0;
混合類型
我們再次之前提到過,接口可以描述很多真實世界中JavaScript的類型,因為JavaScript的動態和天生的靈活性,你或許會遇到一些需要多種復合的類型。
比如下面的實例,這個對象將扮演着函數和一個對象。
--TypeScript
1 interface Counter { 2 (start: number): string; 3 interval: number; 4 reset(): void; 5 } 6 7 var c: Counter; 8 c(10); 9 c.reset(); 10 c.interval = 5.0;
--JavaScript
1 var c; 2 c(10); 3 c.reset(); 4 c.interval = 5.0;
對於需要使用第三方JavaScript庫的情況下,我們就會需要使用到上面介紹的知識。當然現在很多常用的JavaScript庫都已經存在了,我們可以通過nuget獲取到。