interface A {
x: number;
y: number;
}
interface B {
y: number;
z: number;
}
type U = A & B;
type I = A | B;
複製代碼
具體使用屬性時,會發現ts的提示中,交叉類型 U
具備 x,y,z 三個屬性,而聯合類型 I
僅僅只有 y 屬性,這個「聯合」體如今哪裏?html
這裏須要重拾一下什麼是「類型」。拋開「實例化」這些術語,從集合角度出發,類型應該是指 具備特定特性的若干值的集合。git
如在Typescript中,number
類型是全部數字的集合,string
類型是全部字符串的集合,字面量類型 1
是隻 包含一個元素 1 的集合,字面量類型 "1"
是隻包含一個元素 "1" 的集合。github
可是在對象中,集合的概念就很是容易混淆了,我之前一直不能理解爲何說 子類是父類的子集 ,明明子類會比父類擁有更多的屬性和方法,難道不是父類是子類的子集?typescript
這裏是我一直以來都陷入的誤區。看下面例子:安全
interface A {
x: number;
}
interface B extends A {
y: number;
}
const obj = {
x: 1,
xx: 2,
xxx: 3,
};
const a: A = obj;
複製代碼
顯然,變量 a
是 A
類型,可是實際上它所表明的對象,具備 x,xx,xxx 三個屬性。顯然,這裏能夠得出一個重要觀點:對象類型只要求它所描述的對象 有 這些屬性,而不必定是 有且僅有 這些屬性。函數
在這個基礎上,不難理解類型 B
是類型 A
的子集,由於 A
是全部具備 x
屬性的對象的集合(有就行,對象可能有其餘任意屬性),而 B
除了要求對象有 x
屬性,還要有一個 y
屬性,要求明顯比 A
更嚴格,套入集合的觀點,明顯 B
是 A
的子集。ui
即 對象類型是若干對象的集合,而不是屬性的集合。只有一個對象具備它所描述的所有屬性,那麼該對象就是該類型的一員spa
注:Typescript並不徹底基於集合的數學定義。爲了實際的類型安全,若是改爲用字面量對象賦值,將會報錯翻譯
const a: A = {
x: 1,
xx: 2, // ERROR
xxx: 3, // ERROR
};
複製代碼
交叉類型即 Intersection types ,從當前文章角度,翻譯它爲 交集類型 可能會更加合適。設計
聯合類型即 Union types,從當前文章角度,翻譯它爲 並集類型 可能會更加合適。
從集合角度,&
,|
運算符的各類運算結果:
type A = 1 | 2 | 3;
type B = 2 | 3 | 4;
type C = A & B; // C爲: 2 | 3
type D = A & number; // D爲: 1 | 2 | 3
type E = A | number; // E爲: number
type F = number & string; // F爲: never
複製代碼
類型 A-E
套一下集合運算便可。至於類型 F
, 雖然官方對 never
的討論定義只是 「不會出現的類型」 ,但我以爲實際上是 「空集」 的思想在潛移默化的影響他們,把 never
看做是空集就完事了。
至於官方給到的幾個運算特性,估計也是集合的思想在影響着,這些運算特性是顯然易見的:
對於交集運算符 &
:
A & A
等價於 A
.A & B
等價於 B & A
(函數調用和構造函數有些許不一樣,下面會講).(A & B) & C
等價於 A & (B & C)
.B
是 A
的父類型時,A & B
等價於 A
.對於並集運算符 |
:
A | A
等價於 A
.A | B
等價於 B | A
.(A | B) | C
等價於 A | (B & C)
.B
是 A
的子類型時,A | B
等價於 A
.注:Typescript的用詞是 Supertype 和 Subtype,而不是 Superclass 和 Subclass 。也就是 A
和 B
並不必定是 Class
也多是type
或 interface
,相信你能理解這些名詞的差別。
&
比 |
有更高的運算優先級A
的屬性 p
是類型 X
,類型 B
的屬性 p
是類型 Y
,那麼交集類型 A & B
中對應的屬性 p
是 X & Y
A & B
具備類型 A
和 B
的全部屬性(即 屬性的並集 )。再次強調,交集類型 不是屬性的交集,而是 對象的交集。(類型的屬性越多,表明限制條件越多,對象只有知足這些條件的並集,才能歸爲該交集類型)A
和 B
是函數類型,那麼 A & B
表明重載函數,此時集合運算順序即爲重載函數的函數簽名順序(如vscode智能提示中,第一個提示的重載函數類型會是 A
類型)A
是對象類型,B
是原始數據類型(string
,number
這些),那麼把原始數據類型 B
看做是對象類型,它們屬性的並集(好比會包含string
的indexOf
,substr
等方法),就是類型 A & B
具備的屬性。最後兩點可能要結合實際代碼理解:
type M = (a: number) => number;
type N = (a: string) => string;
function overload(a: number): number;
function overload(a: string): string;
function overload(a: number | string) {
return a;
}
let m: M = overload; // OK
let n: N = overload; // OK
let mn: M & N = overload; // OK
複製代碼
顯然類型 M
描述的函數是:可以 接受一個 number
並返回 number
。注意是 可以,而不是 能且只能,和前面的對象類型有殊途同歸之妙。即overload
知足類型 M
是集合 M
的一員,也知足類型 N
是集合 N
的一員,因此它必然是 M & N
這個交集中的元素。
所以最終表現形式會是:函數類型的交集類型是重載函數
其實我也沒什麼特別好的數學解釋,畢竟他們也不是徹底按照嚴格的數學定義來實現的,還有考慮一些實際狀況,有興趣的到這裏圍觀:issues: Intersection types
這個是根據實際狀況定義的,看如下代碼:
let nObj: number & { a: number } = new Number(1) as any;
nObj.a = 2;
console.log(nObj.a); // 打印 2
console.log(++nObj); // 打印 2
console.log(nObj * 10); // 打印 20
let x = Object.assign(1, { a: 2 });
console.log(x.a); // 打印 2
console.log(++x); // 打印 2
console.log(x * 10); // 打印 20
複製代碼
很明顯,number & { a: number }
這種類型是有意義的。通過取捨,最終決定它是成立的,被添加到TS當中。
而 string & number
不能像上面那樣玩,你很難想象 Object.assign(1, "1")
是個什麼東西,雖然它在js中的確存在。各類討論和後續迭代中,最終敲定原始數據類型的交集爲never
注:最後這一點的確想不到怎麼用集合論來解釋,只能額外記憶爲 根據實際狀況特別定義 了。
A
的屬性 p
是類型 X
,類型 B
的屬性 p
是類型 Y
,那麼並集類型 A | B
中對應的屬性 p
是 X | Y
A & B
必定具備 類型 A
和 B
的重複屬性(即 屬性的交集 )。這裏也須要從實際出發,從類型安全角度,在不知道交集中的某個元素具體屬於 A
仍是 B
的狀況下,訪問時 TS只能給你提示它們必定都有的屬性,所以表現上會是交集;賦值 時很少說了,知足A
或 B
就行,標準的 並集 概念。注: 協變與逆變
交叉類型的討論:github.com/microsoft/T…
交叉類型的PR:github.com/microsoft/T…
聯合類型的討論:github.com/microsoft/T…
聯合類型的PR:github.com/microsoft/T…
🤔還有個坑沒填,因此這個「交叉」是...?😂畢竟這裏是TS,不是數學領域,當術語吧