自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

快速理解 TypeScript 的逆變和協(xié)變

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

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

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

類型安全和型變

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

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

這是類型檢查做的事情,遇到類型安全問題會(huì)在編譯時(shí)報(bào)錯(cuò)。

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

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

先來(lái)看下協(xié)變:

協(xié)變

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

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

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

這并不會(huì)報(bào)錯(cuò),雖然這倆類型不一樣,但是依然是類型安全的。

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

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

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

逆變相對(duì)難理解一些:

逆變

我們有這樣兩個(gè)函數(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ù)的子類型。

那么問題來(lái)了,printName 能賦值給 printHobbies 么?printHobbies 能賦值給 printName 么?

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

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

因?yàn)檫@個(gè)函數(shù)調(diào)用的時(shí)候是按照 Guang 來(lái)約束的類型,但實(shí)際上函數(shù)只用到了父類型 Person 的屬性和方法,當(dāng)然不會(huì)有問題,依然是類型安全的。

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

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

因?yàn)楹瘮?shù)聲明的時(shí)候是按照 Person 來(lái)約束類型,但是調(diào)用的時(shí)候是按照 Guang 的類型來(lái)訪問的屬性和方法,那自然類型不安全了,所以就會(huì)報(bào)錯(cuò)。

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

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

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

這樣就支持函數(shù)參數(shù)的雙向協(xié)變,類型檢查不會(huì)報(bào)錯(cuò),但不能嚴(yán)格保證類型安全。

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

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

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

type UnionToIntersection<U> = 
(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ì),讓每個(gè)類型單獨(dú)傳入做計(jì)算,最后合并。

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

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

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

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

不變

非父子類型之間不會(huì)發(fā)生型變,只要類型不一樣就會(huì)報(bào)錯(cuò):

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

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

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

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

還是拿上面那個(gè)例子來(lái)說:

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

沒有呀。

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

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

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

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

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

測(cè)試下:

總結(jié)

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

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

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

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

型變都是針對(duì)父子類型來(lái)說的,非父子類型自然就不會(huì)型變也就是不變。

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

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

責(zé)任編輯:姜華 來(lái)源: 神光的編程秘籍
相關(guān)推薦

2020-09-29 06:37:30

Java泛型

2009-08-03 18:24:28

C# 4.0協(xié)變和逆變

2009-05-27 11:30:20

C#Visual Stud協(xié)變

2020-08-03 08:13:51

Vue3TypeScript

2012-03-13 09:32:15

C#協(xié)變

2011-01-14 10:27:18

C#.netasp.net

2013-10-31 09:36:43

程序員程序高手

2010-01-20 09:17:46

2010-08-17 11:18:19

BISAP商業(yè)智能

2015-02-06 17:00:04

2023-01-29 09:15:42

2018-08-01 15:48:05

搜狗

2018-04-27 16:45:41

華為

2020-12-25 16:52:28

CIO數(shù)字化轉(zhuǎn)型計(jì)世

2015-09-11 10:45:55

服務(wù)器華為

2011-11-17 17:22:10

IT管理云計(jì)算

2011-11-18 10:00:05

云計(jì)算IT管理

2009-07-07 11:04:12

百變?nèi)湎x病毒卡巴斯基
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)