爲什麼引入泛型
如果說接口和類是對一類事物的抽象描述,那麼泛型可以說是對一類數據的抽象描述,進一步加強了語言的抽象程度,讓組件重用性更好。
看一個小例子,實現數組的單條數據插入:
function insertTo<T>(array: T[], index: number, t: T): T[] {
if (index < 0 || array.length - 1 < index) {
throw new Error('Illegal index.');
}
array.splice(index, 0, t);
return array;
}
const arr1 = [1, 2, 4];
insertTo(arr1, 2, 3);
console.log(arr1);
const arr2 = ['a', 'b', 'd'];
insertTo(arr2, 2, 'c');
console.log(arr2);
這個函數使用了泛型來表示傳入參數的類型,我們可以很明顯感覺到泛型的好處:一個函數就可以適配多種類型。用普通方式,我們要重複這段邏輯寫n個函數,用泛型一個就夠用,這就是抽象的好處。
泛型的不同場景
泛型可以用於函數,也可以用於類,還有更高級的用法:帶限定的泛型。後面高級類型,還會用到泛型,會更復雜一些。
泛型函數
上面已給了一個例子,這裏再補充一個用法,限定類型的泛型函數:
interface GenericInsertFn<T> {
<T>(array: T[], index: number, t: T): T[]
}
let myInsertTo: GenericInsertFn<number> = insertTo;
通過類型限定之後,我們就得到了一個新的函數,這個函數只能操作數字類型的數組。
泛型類
泛型類和泛型函數用法類似,只是泛型聲明在類上,整個類範圍都可以用。
看個小例子:
class MyArrayList<T> {
private _array: T[];
constructor() {
this._array = [];
}
get lenght(): number {
return this._array.length;
}
get(index: number): T {
if (index < 0 || this._array.length - 1 < index) {
throw new Error('Illegal index.');
}
return this._array[index];
}
push(...elements: T[]): number {
return this._array.push(...elements);
}
insertTo(index: number, t: T): T[] {
if (index < 0 || this._array.length - 1 < index) {
throw new Error('Illegal index.');
}
this._array.splice(index, 0, t);
return this._array;
}
}
以上例子實現了一個簡單的泛型的ArrayList,如果你熟悉Java語言,對此就不陌生了。實現的效果和函數類似,也是抽象和重用。
泛型的限定
有的場景,泛型需要限定範圍,比如一個任務執行者函數,需要接受實現了工作者接口的函數作爲參數纔可以執行,那麼就需要帶限定的泛型參數,看代碼:
interface IWorker {
(task: string): number;
}
function excutor<W extends IWorker>(r: W) {
r('case1');
}
const myWorker: IWorker = function (task: string) {
console.log(`${task} is start.`);
return 1;
}
excutor(myWorker);
再看一個更復雜一點的用法,多重泛型,抽象度也更高,通用性也更好。
interface IJobRunner<T, K> {
run(t: T): K;
}
interface ITimeJobExcutor<T, K, R extends IJobRunner<T, K>> {
excute(r: R): void;
}
基本的泛型用法學習完了,後面高級類型還會繼續學習更高級的用法。