函數兼容性
函數參數個數
比如有如下兩個函數:
let x = (a: number) => 0
let y = (b: number, c: string) => 0
函數參數個數如果要兼容,需要滿足條件:如果對函數 y 進行賦值,則 x 中的每個參數都應在 y 中有對應,也就是 x 的參數個數小於 y 的參數個數。所以有如下結果:
y = x // 沒問題
x = y // error,不能將類型“(b: number, c: string) => number”分配給類型“(a: number) => number”。
所以在函數兼容性中,參數個數少的可以賦值給參數個數多的。
函數參數類型和返回值類型
除了參數個數,參數的類型也需要對應,有如下三個函數:
let x = (a: number) => 0 let y = (b: string) => 0 let z = (c: string) => false
x 和 y 兩個函數的參數個數和返回值都相同,但是參數類型對應不上,所以互相不兼容:
x = y // error,不能將類型“(b: string) => number”分配給類型“(a: number) => number”。參數“b”和“a” 的類型不兼容。
y 和 z 兩個函數的參數個數和參數類型都相同,但是返回值類型對應不上,所以互相不兼容:
y = z // error,不能將類型“(c: string) => boolean”分配給類型“(b: string) => number”。不能將類型“boolean”分配給類型“number”。
我們來改造下 y 函數,如下:
let y = (b: string): number | boolean => 0 let z = (c: string) => false y = z // 沒問題
y 函數改造后返回聯合類型 number | boolean ,所以可以將 z 賦值給 y。
剩余參數和可選參數
當要被賦值的函數參數中包含剩余參數(...args)時,賦值的函數可以用任意個數的參數代替,但類型要對應,如下:
let x = (...args: number[]):number => 0 let y = (a: number, b: number) => a + b let z = () => 0 x = y // 沒問題 x = z // 沒問題
x 函數中參數是 ...args,表示可以有 0 個參數,也可以有無限多個參數,所以 y 和 z 函數都可以賦值給 x,前提是參數類型是一樣的。
剩余參數可以看成無數個可選參數,來看個可選參數和剩余參數結合的例子:
const getNum = ( arr: number[], // 函數第一個參數是一個各項 number 類型的數組 callback: (arg1: number, arg2?: number) => number // 第二個參數是函數,它有一個或者兩個參數 ): number => { return callback(...arr) // 應有 1-2 個參數,但獲得的數量大於等於 0 }
例子中函數實體里 callback(...arr) 實現了參數中的 callback,callback 參數個數應為 1 個或者 2 個,但是 ...arr 表示參數個數可能是 0 個或者是無限個,當為 0 個時就會不兼容報錯。改成下面這樣就沒問題了:
return callback(arr[0], ...arr)
函數參數雙向協變
函數參數雙向協變即參數類型無需絕對相同,比如:
let A = function(arg: number|string): void{} let B = function(arg: number): void{} A = B // 可以 B = A // 可以
這個例子中,A 和 B 的參數類型並不是完全一樣的,A 的參數類型是一個聯合類型 number | string,而 B 的參數類型是 number | string 中的 number,這兩個函數兼容。但需要注意的是在嚴格模式下,A = B 不通過,B = A 通過。嚴格模式的開啟是在 tsconfig.json 文件中去掉 "strict": true 的注釋。
函數重載
function funcA(arg1: number, arg2: number): number; function funcA(arg1: string, arg2: string): string; function funcA(arg1: any, arg2: any): any{ return arg1 + arg2 } function funcB(arg1: number, arg2: number): number; function funcB(arg1: any, arg2: any): any{ return arg1 + arg2 } let fn = funcA fn = funcB // error,不能將類型“(arg1: number, arg2: number) => number”分配給類型“{ (arg1: number, arg2: number): number; (arg1: string, arg2: string): string; }”
例子中 funcB 的重載缺少參數都為 string 類型,返回值為 string 類型的情況,與函數 funcA 不兼容。
枚舉
數字枚舉成員類型與數字類型兼容,比如:
enum Status{ On, Off } let s = Status.On s = 2 s = 3
Status.On 的值為 0,數字枚舉成員類型和數值類型互相兼容,所以 s 可以賦值為數字類型。
但是不同枚舉類型之間是不兼容的:
enum Status{ On, Off } enum Color{ White, Black } let s = Status.On s = Color.White // error,不能將類型“Color.White”分配給類型“Status”
雖然 Status.On 和 Color.White 的值都為 0,但是它們不兼容。
字符串枚舉成員類型與字符串類型不兼容,比如:
enum Status{ On = 'on', Off = 'off' } let s = Status.On s = 'hi' // error,不能將類型“"hi"”分配給類型“Status”
類
比較兩個類類型值的兼容性時,只比較實例的成員,類的靜態成員和構造函數不進行比較:
class Animal{ static age: number // 靜態屬性 age 是 number 類型 constructor(public name: string){} } class People{ static age: string // 靜態屬性 age 是 string 類型 constructor(public name: string){} } class Food{ constructor(public name: number){} // name 是 number 類型 } let a: Animal let p: People let f: Food a = p // 通過 a = f // error,不能將類型“Food”分配給類型“Animal”。屬性“name”的類型不兼容。
Animal 和 People 的靜態屬性 age 的類型雖然不相同,但是比較兼容性時不比較靜態屬性,而它們都有實例屬性 name,且都為 string 類型,所以 a = p 可行。Food 的實例屬性 name 是 number 類型,所以 a = f 錯誤。
類的私有成員和受保護成員會影響兼容性。比較兩個類的兼容性時,當發現有私有成員或者受保護成員時,只有這些成員來自同一個類時才能兼容,即允許子類賦值給父類:
class Parent{ private age: number constructor(){} } class Children extends Parent{ constructor(){ super() } } class Other{ private age: number constructor(){} } const children: Parent = new Children() const other: Parent = new Other() // error,不能將類型“Other”分配給類型“Parent”。類型具有私有屬性“age”的單獨聲明。
泛型
因為TypeScript是結構性的類型系統,類型參數只影響使用其做為類型一部分的結果類型。比如:
interface Data<T>{} let data1: Data<string> let data2: Data<number> data1 = data2 // 通過
雖然兩次類型參數 T 的類型不一樣,但是因為接口里並沒有用到參數 T,所以不管傳入什么類型都不影響,兩者兼容。相反如果接口中用到參數 T,則結果就相反:
interface Data<T>{ data: T } let data1: Data<string> let data2: Data<number> data1 = data2 // error,不能將類型“Data<number>”分配給類型“Data<string>”。不能將類型“number”分配給類型“string”。
以上就是類型兼容性的相關知識點。