定義:泛型容許咱們延遲編寫類或方法中的編程元素的數據類型的規範,直到實際在程序中使用它的時候。編程
請看以下代碼:數組
function identity<T>(arg: T) {
return arg;
}
function main() {
const result = identity<number>(1);
}
複製代碼
咱們定義了一個泛型方法identity,定義了一個main方法,調用identity方法。bash
定義identity的時候,咱們並不知道參數arg是個什麼類型,T就是一個佔位符,告訴咱們arg是T類型的。編輯器
在調用identity的時候,才告訴它我要給你傳遞一個number類型的,而後傳遞真正的arg參數的值,是1。ide
這就是泛型定義中所說的,延遲編寫方法(==identity==)中的編程元素(==arg參數==)的類型(==不寫number,寫T==),直到實際在程序中(==main方法==)使用它的時候。函數
上例中,將類型(number)變成了函數identity的一個參數,在調用的時候才傳給函數identity,就叫作類型參數化。學習
看完上面的解釋,你多半會疑惑,這麼寫有什麼好處啊,之前那種不也挺好嗎,以下:ui
function identity(arg: number) {
return arg;
}
function main() {
const result = identity(1);
}
複製代碼
這不也實現了相同的邏輯嗎?何須延遲到調用的時候,才肯定參數的類型呢?this
答案是 ==泛型可讓不一樣類型的數據,複用一樣的方法或類。== 若是方法或類在定義的時候,就固定參數的類型,那麼此方法或類只能處理這一種類型的數據。但等調用的時候才肯定參數類型,就可讓多種參數類型的數據,複用同一個方法或類。spa
這裏咱們分析一個稍微複雜的例子,內置對象Array的聲明:
interface Array<T> {
length: number;
toString(): string;
pop(): T | undefined;
reverse(): T[];
sort(compareFn?: (a: T, b: T) => number): this;
...
}
複製代碼
這裏只是列出小部分Array的聲明。我仍是按泛型的定義解釋一邊,在咱們編寫Arry的時候,延遲定義編程元素(這裏指在Array類內能夠使用的T)的數據類型。
使用:
function main() {
const nums = new Array<number>(1, 2, 3);
nums.push(4);
const popNum = nums.pop();
}
複製代碼
直到實際在main函數中使用它的時候。咱們才告訴Array,咱們要一個數字類型的數組。
咱們還能夠
function main() {
const dates = new Array<Date>();
dates.push(new Date('2019-7-8'));
dates.push(new Date('2019-7-9'));
dates.push(new Date('2019-7-10'));
const reverseDates = dates.reverse();
}
複製代碼
注意的問題的關鍵沒,關鍵在於pop,reverse這樣的方法的內在邏輯,是不關心被操縱數據的類型的,因此能夠實現對於不一樣類型的的複用。
因此,Array類就實現類對於不一樣類型的數據列表的相關操做的複用。
若是您是由Javascript爲基礎,學習的TypeScript,或許會以爲上面說的有些莫名其妙,TypeScript搞這個泛型幹啥,JS對此徹底沒有限制啊,沒有類型,能夠隨意傳參啊,以下:
function mainAboutNumber() {
var nums = new Array(1, 2, 3);
nums.push(4);
var popNum = nums.pop();
}
function mainAboutDate() {
var dates = new Array();
dates.push(new Date('2019-7-8'));
dates.push(new Date('2019-7-9'));
dates.push(new Date('2019-7-10'));
var reverseDates = dates.reverse();
}
複製代碼
確實如此,若是沒有類型,就沒有泛型這一說了,泛型是對類型的一種放寬政策,但約束仍舊存在。若是是用JS,這樣的代碼在編寫時不會出錯:
function mainAboutDate() {
var dates = new Array();
dates.push(new Date('2019-7-8'));
dates.push('我不是Date類型');
dates.push(new Date('2019-7-10'));
var reverseDates = dates.reverse();
}
複製代碼
但若是你的業務中,dates是表明一個日期列表,顯然仍是TS更好:
會在咱們編程期間及時報錯,泛型的約束力依舊存在,當給定參數的類型後,咱們就有了一個日期類型的數組了。
除了複用這條優勢,泛型還能夠幫助咱們實現入參和返回值類型一致的校驗,以下:
function identity<T>(arg: T): T {
return arg;
}
複製代碼
如方法後面的冒號+T,就是限定返回值的類型也是T,要和參數arg的T保持一致,當咱們使用它的時候:
將鼠標浮動到num變量上,發現它是個number類型,這是由於咱們將T設置成了numnber。經過類型推斷,num就成了number類型,後面就能夠經過編輯器的智能提示,看到number類型的全部方法了,方便咱們的編程。
在定義方法或類的時候,咱們不光能夠使用T,還能夠爲T增長更多修飾,使其變成一種有更多信息的泛型變量。
以下是Object.freeze方法的簽名。
Object.freeze() 方法能夠凍結一個對象。一個被凍結的對象不再能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對象已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對象後該對象的原型也不能被修改。freeze() 返回和傳入的參數相同的對象。 --- MDN定義
interface ObjectConstructor {
freeze<T>(a: T[]): ReadonlyArray<T>;
...
}
複製代碼
咱們來看freeze(a: T[]): ReadonlyArray簽名。 這個簽名的意思是,若是你傳遞的是個數組(a:T[]),返回值會是一個ReadonlyArray。以下是使用時的代碼:
function main() {
const nums = new Array<number>(1, 2, 3);
const freezeNums = Object.freeze(nums);
}
複製代碼
咱們就獲得了一個readonly的數字數組。這裏的T[]和ReadonlyArray。利用T進一步描述freeze方法的參數和返回值。若是單單用T,顯然信息量不夠多。
另外假設讓咱們來實現freeze方法。那內部咱們就能夠調用a.length這樣的代碼,由於簽名已經明確告訴咱們,入參a是某種數組類型。
泛型約束又是什麼?咱們在看freeze方法的另外一個重載:
interface ObjectConstructor {
freeze<T>(a: T[]): ReadonlyArray<T>;
freeze<T extends Function>(f: T): T;
...
}
複製代碼
不一樣於泛型變量,這回咱們約束尖括號<>裏的T,freeze(f: T): T;的意思是,若是傳遞給freeze的是個方法,那返回值仍是個方法。
再看一個例子:
interface SpecialType {
specialProperty: string;
}
function identity<T extends SpecialType>(arg: T): T {
console.log(arg.specialProperty);
return arg;
}
複製代碼
這個例子中,咱們讓T繼承SpecialType,這樣咱們在identity方法內部,就能夠使用SpecialType規定的屬性了。
泛型約束能夠說增長了泛型的複用能力,可讓某些含有相同屬性的數據,複用一樣須要這些屬性的方法。
interface Array<T> {
...
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
...
}
複製代碼
咱們來分析一下Array類中的map函數,也是咱們常常使用的函數。
第一個參數是個回調函數,回調函數的第一個參數value是T類型,第二個參數index是數字類型,第三個參數是T類型的列表。回調函數的返回值是一個U類型的數據。map函數的返回值是U類型的列表,是個全新類型的列表。
學習泛型,可讓咱們很好的理解第三方庫的TS聲明。也可讓咱們本身寫出優秀的TypeScript代碼。