泛型可以讓你避免重複的程式碼, 用一種清晰和抽像的方式來表達程式碼的意圖.
例如: 有一個交換方法, 它只能交換兩個動物(Animal), 但不能交換數字(number)
在Javascript 世界裡, swap 方法沒辦法明確告知外面 items 輸入參數類型只能限制動物(Animal)
function swap(items) {
var tmp = items[0];
items[0] = items[1];
items[1] = tmp;
...
}
程式開發人員看到這段程式碼, 只知道這個swap 方法可以拿來 "交換", 不清楚應用系統細節的開發人員就很容易拿去誤用.
如果非要加上限制, 那就只能在裡面動手腳, 加上屬性判斷, 但那也因為 items 是 var 任意型別, 外面的人根本就無法一眼看出那是甚麼類型.
function swap(items) {
var tmp = items[0];
if( tmp.AnimalType == undefined) {
throw Error("只能交換動物!!");
}
items[0] = items[1];
items[1] = tmp;
...
}
在Typescript 世界裡, 你只需要改寫成如下
function swap<T>(items: T[]): void {
var tmp = items[0];
items[0] = items[1];
items[1] = tmp;
...
}
是一個泛型符號, 表示任何物件都可以放進去.
但我們這個例子是要限制只有動物(Animal)才能放進去. 所以你可以加上 "緊箍咒"
function swap<T extends Animal>(items: T[]): void {
var tmp = items[0];
items[0] = items[1];
items[1] = tmp;
...
}
這種行為, 在Typescript 叫做泛型約束
瞧! Typescript 是不是很簡單就做到這件事情?
另外Typescript 也支援多個泛型參數, 就跟 C# 泛型一樣簡單
function process<T, U>(a: T, b: U) {
...
}
毫無懸念Javascript 不支援泛型物件, Typescript 支援泛型物件
class Person<T> {
data: T;
}
進一步, Typescript 也支援泛型介面
interface IPerson<T> {
...
}
在 TypeScript 2.3 版本以上, 更是支援我們可以為泛型中的參數指定預設類型
例如
function createArray<T = Person>(length: number, val: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
當使用createArray 方法的時候, 如果沒有直接指定 val 類型參數, 從實際 val 參數值中也無法推斷出的時候, 這個默認類型就會起作用.
let result = createArray(100, val);
Javascript 缺乏對元組的語法支持
例如下面的程式碼片段
let color = [255,0,0];
另外, 每當使用其他數據結構代替元組時, 這些替代數據結構的語義值就會降低, 也就降低開發人員對程式碼的閱讀可讀性.
也有Javascript 用Json 表示這樣的異類型別.
let color = {red: 255, green: 0, blue: 0};
但是這仍然也擺脫不了Javascript 沒有強類型的事實.
在Typescript 世界裡, 你可以這樣宣告明確 Tuple
function getColor(): [number,number,number] {
return [255, 0, 0];
}
我們可以再一次加強語義, 再明確宣告這是個 RGB 型別
type RGB = [number, number, number];
function getColor(): RGB {
return [255, 0, 0];
}
一旦你在VSCode 編輯時, 不小心漏打一個數字, 它會馬上警告你錯誤.
另外Typescript 也能這樣使用陣列形式快速指定變數值,
var [x, y] = [10, 20];
[x, y] = [y, x];
軟體開發設計過程當中, 免不了會遇到策略模式(Strategy Pattern)場景.
策略模式 就是在客戶端(Client) 在執行一個方法(function), 在執行的時候決定要使用哪一種邏輯去跑,無論我們去修改/增加邏輯, 都不會影響到客戶端(Client)呼叫使用的方式.
一個典型的例子就是排序sort 演算法, 但我演算法超爛, 這不用排序演算法示範, 有看過"設計模式"的人都知道"鴨子"的故事.
鴨子故事就是主管要求你設計一隻鴨子, 要求你提供鴨子方法quack() 來輸出鴨子叫. 但是主管要求你要準備許多種類的鴨子, 每一種鴨子的叫聲不一樣.
策略模式(Strategy Pattern) 典型就是要求你設計一個介面(Interface).
甚麼你問我說, 如何在Javascript 裡設計一個介面?
function IDuck() {
this.classType = "IDuck";
this.quack = function () {
}
}
在JavaScript 這樣的 prototype 語言中實現介面(Interface)是很彆扭的.
改成Typescript 世界裡...
interface IDuck {
quack(): void;
}
簡單的三行程式碼搞定一個 IDuck 介面語義說明.
接下來我們用Typescript 實作一隻綠色鴨子呱呱叫
class GreenDuck implements IDuck {
quack(): void {
console.log("呱呱叫");
}
}
再實作紅色鴨子, 這次是嗚嗚叫
class RedDuck implements IDuck {
quack(): void {
console.log("嗚嗚叫");
}
}
然後在客戶端(Client)呼叫 perform 方法
function perform(animal: IDuck): void {
animal.quack();
}
perform(new GreenDuck());
perform(new RedDuck());
瞧, 這世界有Typescript 真的很美好!
Typescript 如同C# 語言一樣, 物件(Class)不支援多重繼承(multiple inheritance), 但支援同時多個 interface
interface IPerson {
say(): void;
}
interface IAnimal {
walk(): void;
}
class AnimalBase {
}
class Student extends AnimalBase implements IAnimal, IPerson {
say(): void {
...
}
walk(): void {
...
}
}
上例中, Student 物件繼承了 AnimalBase 也實現了 IAnimal 和 IPerson 介面(interface)