如何通俗的解釋TypeScript 泛型
概述
在 TypeScript 中我們會使用泛型來對函數(shù)的相關(guān)類型進行約束。這里的函數(shù),同時包含 class 的構(gòu)造函數(shù),因此,一個類的聲明部分,也可以使用泛型。那么,究竟什么是泛型?如果通俗的理解泛型呢?
什么是泛型
泛型(Generics)是指在定義函數(shù)、接口或類的時候,不預(yù)先指定具體的類型,而在使用的時候再指定類型的一種特性。
通俗的解釋,泛型是類型系統(tǒng)中的“參數(shù)”,主要作用是為了類型的重用。從上面定義可以看出,它只會用在函數(shù)、接口和類中。它和js程序中的函數(shù)參數(shù)是兩個層面的事物(雖然意義是相同的),因為 typescript 是靜態(tài)類型系統(tǒng),是在js進行編譯時進行類型檢查的系統(tǒng),因此,泛型這種參數(shù),實際上是在編譯過程中的運行時使用。之所以稱它為“參數(shù)”,是因為它具備和函數(shù)參數(shù)一模一樣的特性。
function increse(param) { // ... }
而類型系統(tǒng)中,我們?nèi)绱耸褂梅盒停?/p>
function increase<T>(param: T): T { //... }
當 param 為一個類型時,T 被賦值為這個類型,在返回值中,T 即為該類型從而進行類型檢查。
編譯系統(tǒng)
要知道 typescript 本身的類型系統(tǒng)也需要編程,只不過它的編程方式很奇怪,你需要在它的程序代碼中穿插 js代碼(在 ts 代碼中穿插 js 代碼這個說法很怪,因為我們直觀的感覺是在 js 代碼中夾雜了 ts 代碼)。
編程中,最重要的一種形式就是函數(shù)。在 typescript 的類型編程中,你看到函數(shù)了嗎?沒有。這是因為,有泛型的地方就有函數(shù),只是函數(shù)的形式被 js 代碼給割裂了。typescript 需要進行編譯后得到最終產(chǎn)物。編譯過程中要做兩件事,一是在內(nèi)存中運行類型編程的代碼,從而形成類型檢查體系,也就是說,我們能夠?qū)?js 代碼進行類型檢查,首先是 typescript 編譯器運行 ts 編程代碼后得到了一個運行時的檢查系統(tǒng)本文來自否子戈的播客,運行這個系統(tǒng),從而對穿插在其中的 js 代碼進行類型斷言;二是輸出 js,輸出過程中,編譯系統(tǒng)已經(jīng)運行完了類型編程的代碼,就像php代碼中 echo js 代碼一樣,php代碼已經(jīng)運行了,顯示出來的是 js 代碼。
從這個角度看 typescript,你或許更能理解為什么說它是JavaScript的超集,為什么它的編譯結(jié)果是 js。
通俗的理解泛型
既然我們理解了 ts 編譯系統(tǒng)的邏輯,那么我們就可以把類型的編程和 js 本身的業(yè)務(wù)編程在情感上區(qū)分開。我們所講的“泛型”,只存在于類型編程的部分,這部分代碼是 ts 的編譯運行時代碼。
我們來看下一個簡單的例子:
function increase<T>(param: T): T { //... }
這段代碼,如果我們把 js 代碼區(qū)分開,然后用類型描述文本來表示會是怎樣?
// 聲明函數(shù) @type,參數(shù)為 T,返回結(jié)果為 (T): T @type = T => (T): T // 運行函數(shù)得到一個類型 F,即類型為 (number): number @F = @type(number) // 要求 increase 這個函數(shù)符合 F 這種類型,也就是參數(shù)為 number,返回值也為 number @@F function increase(param) { // ... } @@end
實際上沒有 @@F 這種語法,是我編造出來的,目的是讓你可以從另一個角度去看類型系統(tǒng)。
當我們理解泛型是一種“參數(shù)”之后,我們可能會問:類型系統(tǒng)的函數(shù)在哪里?對于 js 函數(shù)而言,你可以很容易指出函數(shù)聲明語句和參數(shù),但是 ts 中,這個部分是隱藏起來的。不過,我們可以在一些特定結(jié)構(gòu)中,比較容易看到類型函數(shù)的影子:
// 聲明一個泛型接口,這個寫法,像極了聲明一個函數(shù),我們用描述語言來形容 @type = T => (T): T interface GenericIdentityFn<T> { (arg: T): T; } // 這個寫法,有點像一個閉包函數(shù),在聲明函數(shù)后,立即運行這個函數(shù),描述語言:@@[T => (T): T](any) function identity<T>(arg: T): T { return arg; } // 使用泛型接口,像極了調(diào)用一個函數(shù),我們用描述語言來形容 @type(number) let myIdentity: GenericIdentityFn<number> = identity;
上面這一整段代碼,我們用描述文本重寫一遍:
@GenericIdentityFn = T => (T): T @@[T => (T): T](any) function identify(arg) { return arg } @@end @@GenericIdentityFn(number) let myIdentity = identity @@end
我們在類型系統(tǒng)中聲明了兩個函數(shù),分別是 @GenericIdentityFn 和 @some(匿名函數(shù) @[T => (T): T])。雖然是兩個函數(shù),但是實際上,它們的是一模一樣的,因為 typescript 是結(jié)構(gòu)類型,也就是在類型檢查的時候只判斷結(jié)構(gòu)上的每個節(jié)點類型是否相同,而不是必須保持類型變量本身的指針相同。@GenericIdentityFn 和 @some 這兩個函數(shù)分別被調(diào)用,用來修飾 identify 和 myIdentify,在調(diào)用的時候,接收的參數(shù)不同,所以導(dǎo)致最終的類型檢查規(guī)則是不同的,identify 只要保證參數(shù)和返回值的類型相同,至于具體什么類型,any。而 myIdentify 除了保證參數(shù)返回值類型相同外,還要求類型必須是 number。
泛型類
除了泛型接口,class 類也可以泛型化,即“泛型類”,借助泛型類,我們來探究一下泛型的聲明和使用的步驟。
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>();
前文泛型接口因為只是為了約束函數(shù)的類型,所以寫的很像函數(shù),實際上,我們可以用描述語言重新描述一個泛型接口和泛型類。上面的紅色部分,我們用描述語言來描述:
@GenericNumber = T => class { zeroValue: T; add: (x: T, y: T) => T; }
@GenericNumber 這個函數(shù),以 T 為參數(shù),返回一個 class,在 @type 函數(shù)體內(nèi)多次用到了參數(shù) T。
@GenericIdentityFn = T => interface { (arg: T): T; }
我們重新描述了前面的 interface GenericIdentityFn,這樣我們就可以在接口中增加其他的方法。
可以注意到,即使 typescript 內(nèi)置的基礎(chǔ)類型,例如 Array,被聲明為泛型接口、泛型類之后,這些接口和類在使用時必須通過<>傳入?yún)?shù),本質(zhì)上,因為它們都是函數(shù),只是返回值不同。
其他泛型使用的通俗解釋
接下來我們要再描述一個復(fù)雜的類型:
class Animal { numLegs: number; } function createInstance<A extends Animal>(c: new () => A): A { return new c(); }
我們姑且不去看 new() 的部分,我們看尖括號中的 extends語法,這里應(yīng)該怎么理解呢?實際上,我們面對的問題是,在編譯時,<A extends Animal> 尖括號中的內(nèi)容是什么時候運行的,是之前,還是之間?
// 到底是 @type = (A extends Animal) => (new() => A): A @type(T) // 還是 @type = A => (new() => A): A @type(T extends Animal)復(fù)
因為 typescript 是靜態(tài)類型系統(tǒng),Animal 是不變的類,因此,可以推測其實在類的創(chuàng)建之前,尖括號的內(nèi)容已經(jīng)被運行了。
@type = (A extends Animal) => (new() => A): A
也就是說,要使用 @type(T) 產(chǎn)生類型,首先 T 要滿足 Animal 的結(jié)構(gòu),然后才能得到需要的類型,如果 T 已經(jīng)不滿足 Animal 類的結(jié)構(gòu)了,那么編譯器會直接報錯,而這個報錯,不是類型檢查階段,而是在類型系統(tǒng)的創(chuàng)建階段,也就是 ts 代碼的運行階段。這種情況被稱為“泛型約束”。
另外,類似 <A,B> 這樣的語法其實和函數(shù)參數(shù)一致。
@type = (A, B) => (A|B): SomeType
我們再來看 ts 內(nèi)置的基礎(chǔ)類型:Array<number>
@Array = any => any[]
結(jié)語
Typescript 中的泛型,實際上就是類型的生成函數(shù)的參數(shù)。本文的內(nèi)容全部為憑空想象,僅適用于對 ts 進行理解時的思路開拓,不適用于真實編程,特此聲明。
以上就是如何通俗的解釋TypeScript 泛型的詳細內(nèi)容,更多關(guān)于TypeScript泛型的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
深入理解JS中的微任務(wù)和宏任務(wù)的執(zhí)行順序及應(yīng)用場景
JavaScript中的任務(wù)分為宏任務(wù)和微任務(wù),它們的執(zhí)行順序會影響代碼的執(zhí)行結(jié)果。了解它們的機制可以幫助我們更好地理解事件循環(huán)和異步編程,避免出現(xiàn)一些意想不到的錯誤2023-05-05JavaScript實現(xiàn)簡單的文本逐字打印效果示例
這篇文章主要介紹了JavaScript實現(xiàn)簡單的文本逐字打印效果,涉及javascript結(jié)合時間函數(shù)動態(tài)操作頁面元素相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2018-04-04利用 JavaScript 實現(xiàn)并發(fā)控制的示例代碼
這篇文章主要介紹了利用 JavaScript 實現(xiàn)并發(fā)控制的示例代碼,本文通過實例代碼給大家介紹的非常想詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12JS+HTML5本地存儲Localstorage實現(xiàn)注冊登錄及驗證功能示例
這篇文章主要介紹了JS+HTML5本地存儲Localstorage實現(xiàn)注冊登錄及驗證功能,結(jié)合實例形式分析了基于JS+HTML5本地存儲Localstorage實現(xiàn)注冊登錄及驗證相關(guān)操作技巧,需要的朋友可以參考下2020-02-02JS判斷輸入的字符串是否是數(shù)字的方法(正則表達式)
下面小編就為大家?guī)硪黄狫S判斷輸入的字符串是否是數(shù)字的方法(正則表達式)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11