面向對象特性中,最根本的就是面向對象的三大基本特征:封裝、繼承、多態。同時,TypeScript中也存在多態的使用,比如函數重載,今天我們先看一下函數重載以及泛型的概念。
什么是函數重載
簡單來說,函數重載具有兩個特征:名稱相同,參數不同(參數類型、個數不同。)所以,函數重載的解釋應該是具備不同參數的同名函數。注意:函數重載是多態的一種體現。
函數重載的聲明和實現
TypeScript中,函數重載主要包括兩部分:函數聲明,和函數實現。函數聲明主要是TSC解析的一種聲明體現,實際編譯中,並不會編譯成具體代碼。我們可以通過TypeScript的playground來查看。
1、參數不同的函數重載
加入我們有一個打印函數,可以打印輸入的一個string信息,我們可以將函數聲明如下:
// 函數聲明 function print(info: string): void;
而還有另一種情況,就是輸入的有可能是兩個string類型的參數,我們都需要打印下來,於是我們的函數聲明可以是這樣:
// 函數聲明 function print(info: string, message: string): void;
而當這兩種聲明,同時存在TypeScript的聲明文件中,我們就需要用函數重載來實現,這是JavaScript沒有的特性。
而實現函數重載的要求就是,我們要在一個更為寬泛的范圍去實現函數重載,所以,TypeScript中的我們實現print函數如下:
// 在更寬泛的范圍,我們用可選參數來實現重載
function print(info: string, message ?: string) {
let printValue: string = info;
if(message){
printValue += message;
}
console.log(printValue);
}
2、參數個數相同,但類型不同的函數重載
函數重載的第二種情況,參數個數相同,但是參數類型不一樣,這種情況下也可以通過重載來實現。
比如,上述打印信息的函數,有可能接受的輸入是一個string字符串,也有可能輸入接受的是一個number類型的數字,那么我們第一步的函數聲明便是如下:
function print(info: string): void; function print(num: number): void;
從上可以看到,我們的函數聲明中,參數的類型是不同的,在這種情況下,TypeScript是如何在一個寬泛的范圍內實現呢?這里就要用到聯合類型,如下所示:
function print(message: string | number) {
console.log(message)
}
函數重載的總結
從我們實現兩個函數重載的例子可以看出,我們在TypeScript中實現函數重載的方式分別是利用了TypeScript中的兩個類型特性:可選類型以及聯合類型。
所以,如果從便捷的角度來講,我們如果是遇到了類似的實現,其實可以直接使用可選參數和聯合類型來實現自己想要的函數效果。
泛型
在函數重載的不同參數類型,相同參數個數的重載中,我們介紹了它的重載實現方式,利用聯合類型來實現,但是如果要打印出來的類型有很多,那么我們最終只能用any類型來實現print函數了。
但是,如果用any類型實現一個可以打印任意值的print函數,這樣又讓我們的函數變得類型缺失,這個時候,泛型這種解決方案也就應運而生。
什么是泛型
泛型指的是一種情況:定義是可以是任意類型,但是在編譯的時候,必須有明確的類型。
有點繞,那么我們用泛型來實現上述第二個函數重載的例子,結合這個例子,可以體會一下這句話的含義。
function print<T>(message: T) {
console.log(message);
}
在這個函數中,泛型表示的方式是:函數名稱<泛型參數>(arg: 泛型參數)。
這個函數在聲明之后,函數類型是一個泛型。我們可以傳遞任意的類型參數到print函數中,但是當我們傳遞一個string類型的時候,這個函數便是一個string類型的函數了,已經在tsc編譯階段開始明確指定類型,這是和any函數所不一樣的地方。
泛型的好處
首先,我們不用定義過多的聯合類型來讓函數變得復雜而又冗長,如:
function print(arg: string | number | boolean | array | 自定義類型) {
// 我們應該盡量避免多類型的傳值函數,此時我們應該用泛型來實現。
}
其次,泛型可以是任何類型,但是在編譯時一定是類型確定的。而且泛型也可以有繼承屬性,可以繼承接口獲取更多的類型定義等。
function print<T extends Interface> (arg: T) {
// 通過繼承,來讓泛型有更多的可變性。
}
最后,類型別名也可以是泛型,如我們可以做如下類型定義:
type Person<T> = { age: T }
泛型總結
總體來說,利用泛型,也是為了更准確的讓我們使用類型思維,是為了更准確的描述參數、或者聲明的類型准確性,如果能夠熟練的掌握泛型,那么在TypeScript的開發中,將會有不一樣的體驗。
而常常以類型思維去思考JavaScript中的函數或者變量,我們也就會減少很多因為類型方面的犯錯,使得我們的項目不僅更好的測試,也會更少的出錯。
不得不說,在大前端領域,類型思維的缺失的確是個普遍現象,如果將類型思維撿起來,將會是一個可能存在着痛苦的過程,但是我相信,如果你做到了,那么你不僅會在開發代碼的時候會更謹慎,能開發出更優秀的應用程序,還會體驗到前端行業別樣的魅力。
