日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
快速理解TypeScript的逆變和協(xié)變

深入學(xué)習(xí) TypeScript 類型系統(tǒng)的話,逆變、協(xié)變、雙向協(xié)變、不變是繞不過去的概念。

為黃石等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及黃石網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為網(wǎng)站設(shè)計制作、成都網(wǎng)站建設(shè)、黃石網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!

這些概念看起來挺高大上的,其實并不復(fù)雜,這篇文章我們就來學(xué)習(xí)下它們吧。

類型安全和型變

TypeScript 給 JavaScript 添加了一套靜態(tài)類型系統(tǒng),是為了保證類型安全的,也就是保證變量只能賦同類型的值,對象只能訪問它有的屬性、方法。

比如 number 類型的值不能賦值給 boolean 類型的變量,Date 類型的對象就不能調(diào)用 exec 方法。

這是類型檢查做的事情,遇到類型安全問題會在編譯時報錯。

但是這種類型安全的限制也不能太死板,有的時候需要一些變通,比如子類型是可以賦值給父類型的變量的,可以完全當(dāng)成父類型來使用,也就是“型變”(類型改變)。

這種“型變”分為兩種,一種是子類型可以賦值給父類型,叫做協(xié)變,一種是父類型可以賦值給子類型,叫做逆變。

先來看下協(xié)變:

協(xié)變

其中協(xié)變是很好理解的,比如我們有兩個 interface:

interface Person {
name: string;
age: number;
}
interface Guang {
name: string;
age: number;
hobbies: string[]
}

這里 Guang 是 Person 的子類型,更具體,那么 Guang 類型的變量就可以賦值給 Person 類型:

這并不會報錯,雖然這倆類型不一樣,但是依然是類型安全的。

這種子類型可以賦值給父類型的情況就叫做協(xié)變。

為什么要支持協(xié)變很容易理解:類型系統(tǒng)支持了父子類型,那如果子類型還不能賦值給父類型,還叫父子類型么?

所以型變是實現(xiàn)類型父子關(guān)系必須的,它在保證類型安全的基礎(chǔ)上,增加了類型系統(tǒng)的靈活性。

逆變相對難理解一些:

逆變

我們有這樣兩個函數(shù):

let printHobbies: (guang: Guang) => void;
printHobbies = (guang) => {
console.log(guang.hobbies);
}
let printName: (person: Person) => void;
printName = (person) => {
console.log(person.name);
}

printHobbies 的參數(shù)是 printName 參數(shù)的子類型。

那么問題來了,printName 能賦值給 printHobbies 么?printHobbies 能賦值給 printName 么?

測試一下發(fā)現(xiàn)是這樣的:

printName 的參數(shù)不是 printHobbies 的父類型么,為啥能賦值給子類型?

因為這個函數(shù)調(diào)用的時候是按照 Guang 來約束的類型,但實際上函數(shù)只用到了父類型 Person 的屬性和方法,當(dāng)然不會有問題,依然是類型安全的。

這就是逆變,函數(shù)的參數(shù)有逆變的性質(zhì)(而返回值是協(xié)變的,也就是子類型可以賦值給父類型)。

那反過來呢,如果 printHoobies 賦值給 printName 會發(fā)生什么?

因為函數(shù)聲明的時候是按照 Person 來約束類型,但是調(diào)用的時候是按照 Guang 的類型來訪問的屬性和方法,那自然類型不安全了,所以就會報錯。

但是在 ts2.x 之前支持這種賦值,也就是父類型可以賦值給子類型,子類型可以賦值給父類型,既逆變又協(xié)變,叫做“雙向協(xié)變”。

但是這明顯是有問題的,不能保證類型安全,所以之后 ts 加了一個編譯選項 strictFunctionTypes,設(shè)置為 true 就只支持函數(shù)參數(shù)的逆變,設(shè)置為 false 則是雙向協(xié)變。

我們把 strictFunctionTypes 關(guān)掉之后,就會發(fā)現(xiàn)兩種賦值都可以了:

這樣就支持函數(shù)參數(shù)的雙向協(xié)變,類型檢查不會報錯,但不能嚴格保證類型安全。

開啟之后,函數(shù)參數(shù)就只支持逆變,子類型賦值給父類型就會報錯:

在類型編程中這種逆變性質(zhì)有什么用呢?

還記得之前聯(lián)合轉(zhuǎn)交叉的實現(xiàn)么?

type UnionToIntersection = 
(U extends U ? (x: U) => unknown : never) extends (x: infer R) => unknown
? R
: never

類型參數(shù) U 是要轉(zhuǎn)換的聯(lián)合類型。

U extends U 是為了觸發(fā)聯(lián)合類型的 distributive 的性質(zhì),讓每個類型單獨傳入做計算,最后合并。

利用 U 做為參數(shù)構(gòu)造個函數(shù),通過模式匹配取參數(shù)的類型。

結(jié)果就是交叉類型:

我們通過構(gòu)造了多個函數(shù)類型,然后模式提取參數(shù)類型的方式,來實現(xiàn)了聯(lián)合轉(zhuǎn)交叉,這里就是因為函數(shù)參數(shù)是逆變的,會返回聯(lián)合類型的幾個類型的子類型,也就是更具體的交叉類型。

逆變和協(xié)變都是型變,是針對父子類型而言的,非父子類型自然就不會型變,也就是不變:

不變

非父子類型之間不會發(fā)生型變,只要類型不一樣就會報錯:

那類型之間的父子關(guān)系是怎么確定的呢,好像也沒有看到 extends 的繼承?

類型父子關(guān)系的判斷

像 java 里面的類型都是通過 extends 繼承的,如果 A extends B,那 A 就是 B 的子類型。這種叫做名義類型系統(tǒng)(nominal type)。

而 ts 里不看這個,只要結(jié)構(gòu)上是一致的,那么就可以確定父子關(guān)系,這種叫做結(jié)構(gòu)類型系統(tǒng)(structual type)。

還是拿上面那個例子來說:

Guang 和 Person 有 extends 的關(guān)系么?

沒有呀。

那是怎么確定父子關(guān)系的?

通過結(jié)構(gòu),更具體的那個是子類型。這里的 Guang 有 Person 的所有屬性,并且還多了一些屬性,所以 Guang 是 Person 的子類型。

注意,這里用的是更具體,而不是更多。

判斷聯(lián)合類型父子關(guān)系的時候, 'a' | 'b' 和 'a' | 'b' | 'c' 哪個更具體?

'a' | 'b' 更具體,所以 'a' | 'b' 是 'a' | 'b' | 'c' 的子類型。

測試下:

總結(jié)

ts 通過給 js 添加了靜態(tài)類型系統(tǒng)來保證了類型安全,大多數(shù)情況下不同類型之間是不能賦值的,但是為了增加類型系統(tǒng)靈活性,設(shè)計了父子類型的概念。父子類型之間自然應(yīng)該能賦值,也就是會發(fā)生型變。

型變分為逆變和協(xié)變。協(xié)變很容易理解,就是子類型賦值給父類型。逆變主要是函數(shù)賦值的時候函數(shù)參數(shù)的性質(zhì),參數(shù)的父類型可以賦值給子類型,這是因為按照子類型來聲明的參數(shù),訪問父類型的屬性和方法自然沒問題,依然是類型安全的。但反過來就不一定了。

不過 ts 2.x 之前反過來依然是可以賦值的,也就是既逆變又協(xié)變,叫做雙向協(xié)變。

為了更嚴格的保證類型安全,ts 添加了 strictFunctionTypes 的編譯選項,開啟以后函數(shù)參數(shù)就只支持逆變,否則支持雙向協(xié)變。

型變都是針對父子類型來說的,非父子類型自然就不會型變也就是不變。

ts 中父子類型的判定是按照結(jié)構(gòu)來看的,更具體的那個是子類型。

理解了如何判斷父子類型(結(jié)構(gòu)類型系統(tǒng)),父子類型的型變(逆變、協(xié)變、雙向協(xié)變),很多類型兼容問題就能得到解釋了。


分享標題:快速理解TypeScript的逆變和協(xié)變
文章位置:http://www.dlmjj.cn/article/dpcsjdp.html