TypeScript中類型兼容性的示例詳解
JavaScript 是一門弱類型語言,它對類型是弱校驗(yàn),正因?yàn)檫@個特點(diǎn),所以才有了TypeScript這個強(qiáng)類型語言系統(tǒng)的出現(xiàn),來彌補(bǔ)類型檢查的短板。TypeScript在實(shí)現(xiàn)類型強(qiáng)校驗(yàn)的同時,還要滿足 JavaScript 靈活的特點(diǎn),所以就有了類型兼容性這個概念。了解類型兼容性可以避免在實(shí)際的開發(fā)中出現(xiàn)一些低級錯誤。下面就來看看類型兼容性的概念和分類。
1. 類型兼容性的概念
所謂的類型兼容性用于確定一個類型是否能賦值給其他類型。TypeScript中的類型兼容性是基于結(jié)構(gòu)類型的,結(jié)構(gòu)類型是一種只使用其成員來描述類型的方式。其基本原則是,如果 x 要兼容 y,那么 y 至少要具有與 x 相同的屬性。
下面來看一個例子,構(gòu)建一個 Teacher
類 ,然后聲明一個接口 Student
,Student
的屬性 Teacher
都有,而且還多了其他的屬性,這種情況下 Student
就兼容了 Teacher
:
class?Teacher?{ ????constructor(public?weight:?number,?public?name:?string,?public?job:?string)?{ ???? ????} }</code><code>interface?Student?{ ????name:?string ????weight:?number }</code><code>let?x:?Student; x?=?new?Teacher(120,?'TS',?'teacher')?//??
如果反過來,Teacher
并沒有兼容 Student
,因?yàn)?nbsp;Student
的屬性比 Person
少一個。
2. 特殊類型的類型兼容性
先來看看 TypeScript 中一些特殊類型的類型兼容性。
(1)any
any
類型可以賦值給除了 never
之外的任意其他類型,反過來其他類型也可以賦值給 any
。也就是說 any
可以兼容除了 never
之外的所有類型,同時也可以被所有的類型兼容。
let?any:?any; let?a:?number?=?any;???????//?? let?b:?{}?=?any;???????????//?? let?b:?()?=>?number?=?any;?//??
(2)never
never
類型可以賦值給任何其他類型,但不能被其他任何類型賦值。
let?never:?never?=?(()?=>?{ ??throw?Error('never'); })(); let?a:?number?=?never;???????//?? let?b:?()?=>?number?=?never;?//?? let?c:?{}?=?never;???????????//??
可以看到,這里將 never
類型賦值給了 number
、函數(shù)、對象類型,都是沒有問題的。
(3)unknown
unknown
和 never
的特性是相反的,即不能把 unknown
賦值給除了 any
之外的任何其他類型,但其他類型都可以賦值給 unknown
。
let?unknown:?unknown; const?a:?number?=?unknown;???????//?不能將類型“unknown”分配給類型“number”。 const?b:?()?=>?number?=?unknown;?//?不能將類型“unknown”分配給類型“()?=> number”。 const?c:?{}?=?unknown;???????????//?不能將類型“unknown”分配給類型“{}”。
可以看到,當(dāng)把 unknown
類型賦值給 number
、函數(shù)、對象類型時,都報錯了,這就是因?yàn)轭愋椭g不能兼容。
3. 函數(shù)類型的類型兼容性
函數(shù)的類型兼容性主要包括以下六個方面:
(1)參數(shù)數(shù)量
函數(shù)參數(shù)數(shù)量要想兼容,需要滿足一個要求:如果將函數(shù) y 賦值為 x,那么要求 x 中的每個參數(shù)都應(yīng)在 y 中有對應(yīng),也就是 x 的參數(shù)個數(shù)小于等于 y 的參數(shù)個數(shù):
let?x?=?(a:?number)?=>?0; let?y?=?(b:?number,?c:?string)?=>?0;
上面定義的兩個函數(shù),如果進(jìn)行賦值的話,來看下兩種情況的結(jié)果:
y?=?x;??//??
將 x 賦值給 y 是可以的,因?yàn)?x 的參數(shù)個數(shù)小于等于 y 的參數(shù)個數(shù),而至于參數(shù)名是否相同是無所謂的。
而將 y 賦值給 x 就不可以了:
x?=?y;?//?不能將類型“(b: number, c: string)?=> number”分配給類型“(a: number)?=> number”。
這里 y 的參數(shù)個數(shù)要大于 x,所以報錯了。
(2)函數(shù)參數(shù)類型
除了參數(shù)數(shù)量,參數(shù)的類型也需要對應(yīng):
let?x?=?(a:?number)?=>?0; let?y?=?(b:?string)?=>?0; x?=?y;?// error 不能將類型“(b: string)?=> number”分配給類型“(a: number)?=> number”。
可以看到,x 和 y 兩個函數(shù)的參數(shù)個數(shù)和返回值都相同,只是參數(shù)類型對不上,所以也是不行的。
(3)剩余參數(shù)和可選參數(shù)
當(dāng)要被賦值的函數(shù)參數(shù)中包含剩余參數(shù)(…args
)時,賦值的函數(shù)可以用任意個數(shù)參數(shù)代替,但是類型需要對應(yīng):
const?getNum?=?( ??arr:?number[], ??callback:?(...args:?number[])?=>?number ):?number?=>?{ ??return?callback(...arr); }; getNum( ??[1,?2], ??(...args:?number[]):?number?=>?args.length?//?返回參數(shù)的個數(shù) );
剩余參數(shù)其實(shí)可以看做無數(shù)個可選參數(shù),所以在兼容性方面是差不多的。
再來看一個可選參數(shù)和剩余參數(shù)結(jié)合的例子:
//?第二個參數(shù)callback是一個函數(shù),函數(shù)的第二個參數(shù)為可選參數(shù) const?getNum?=?( ??arr:?number[], ??callback:?(arg1:?number,?arg2?:?number)?=>?number? ):?number?=>?{ ??return?callback(...arr);?//?error?應(yīng)有?1-2?個參數(shù),但獲得的數(shù)量大于等于?0 };
這里因?yàn)?nbsp;arr
可能為空數(shù)組,如果為空數(shù)組則…arr
不會給callback
傳入任何實(shí)際參數(shù),所以這里就會報錯。如果換成return callback(arr[0], …arr)
就沒問題了。
(4)參數(shù)雙向協(xié)變
函數(shù)參數(shù)雙向協(xié)變即參數(shù)類型無需絕對相同:
let?funcA?=?function(arg:?number?|?string):?void?{}; let?funcB?=?function(arg:?number):?void?{}; //?funcA?=?funcB?和?funcB?=?funcA都可以
這里 funcA
和 funcB
的參數(shù)類型并不完全一樣,funcA
的參數(shù)類型為一個聯(lián)合類型 number | string
,而 funcB
的參數(shù)類型為 number | string
中的 number
,這兩個函數(shù)也是兼容的。
(5)返回值類型
函數(shù)返回值的類型也是要對應(yīng)的:
let?x?=?(a:?number):?string?|?number?=>?0; let?y?=?(b:?number)?=>?"a"; let?z?=?(c:?number)?=>?false; x?=?y;?//?? x?=?z;?//?不能將類型“(c:?number)?=>?boolean”分配給類型“(a:?number)?=>?string?|?number”
這里 x 函數(shù)的返回值是聯(lián)合類型,既可以是 string
類型也可以是 number
類型。而 y 的返回值類型是 number
類型,參數(shù)個數(shù)和類型也沒問題,所以可以賦值給 x。而 z 的返回值類型 false
并不是 string
也不是 number
,所以不能賦值。
(6)函數(shù)重載
帶有重載的函數(shù),要求被賦值的函數(shù)的每個重載都能在用來賦值的函數(shù)上找到對應(yīng)的簽名:
function?merge(arg1:?number,?arg2:?number):?number;?//?merge函數(shù)重載的一部分 function?merge(arg1:?string,?arg2:?string):?string;?//?merge函數(shù)重載的一部分 function?merge(arg1:?any,?arg2:?any)?{?//?merge函數(shù)實(shí)體 ??return?arg1?+?arg2; } function?sum(arg1:?number,?arg2:?number):?number;?//?sum函數(shù)重載的一部分 function?sum(arg1:?any,?arg2:?any):?any?{?//?sum函數(shù)實(shí)體 ??return?arg1?+?arg2; } let?func?=?merge; func?=?sum;?//?error?不能將類型“(arg1:?number,?arg2:?number)?=>?number”分配給類型“{?(arg1:?number,?arg2:?number):?number;?(arg1:?string,?arg2:?string):?string;?}”
sum
函數(shù)的重載缺少參數(shù)都為string
返回值為string
的情況,與merge
函數(shù)不兼容,所以賦值時就會報錯。
4. 枚舉的類型兼容性
數(shù)字枚舉成員類型與數(shù)字類型是互相兼容的:
enum?Status?{ ??On, ??Off } let?s?=?Status.On; s?=?1; s?=?3;
雖然 Status.On
的值是 0,但是因?yàn)閿?shù)字枚舉成員類型和數(shù)值類型是互相兼容的,所以這里給s
賦值為 3 是沒問題的。但是不同枚舉值之間是不兼容的:
enum?Status?{ ??On, ??Off } enum?Color?{ ??White, ??Black } let?s?=?Status.On; s?=?Color.White;?//?不能將類型“Color.White”分配給類型“Status”。
雖然 Status.On
和 Color.White
的值都是 0,但它們是不兼容的。
字符串枚舉成員類型和字符串類型是不兼容的:
enum?Status?{ ??On?=?'on', ??Off?=?'off' } let?s?=?Status.On s?=?'TypeScript'?//?不能將類型"TypeScript"分配給類型“Status”
這里會報錯,因?yàn)樽址置媪款愋?code>'TypeScript'和Status.On
是不兼容的。
4. 類類型的類型兼容性
比較兩個類的類型兼容性時,只有實(shí)例成員和方法會相比較,類的靜態(tài)成員和構(gòu)造函數(shù)不進(jìn)行比較:
class?Animal?{ ??static?age:?number; ??constructor(public?name:?string)?{} } class?People?{ ??static?age:?string; ??constructor(public?name:?string)?{} } class?Food?{ ??constructor(public?name:?number)?{} } let?a:?Animal; let?p:?People; let?f:?Food; a?=?p;?//?ok a?=?f;?//?不能將類型“Food”分配給類型“Animal”。
Animal
類和People
類都有一個age
靜態(tài)屬性,它們都定義了實(shí)例屬性name
,類型是string
。把類型為People
的p
賦值給類型為Animal
的a
是沒有問題的,因?yàn)轭愵愋捅容^兼容性時,只比較實(shí)例的成員,這兩個變量雖然類型是不同的類類型,但是它們都有相同字段和類型的實(shí)例屬性name
,而類的靜態(tài)成員是不影響兼容性的,所以它倆時兼容的。而類Food
定義了一個實(shí)例屬性name
,類型為number
,所以類型為Food
的f
與類型為Animal
的a
類型是不兼容的,不能賦值。
類的私有成員和受保護(hù)成員:
類的私有成員和受保護(hù)成員會影響類的兼容性。當(dāng)檢查類的實(shí)例兼容性時,如果目標(biāo)(要被賦值的那個值)類型(這里實(shí)例類型就是創(chuàng)建它的類)包含一個私有成員,那么源(用來賦值的值)類型必須包含來自同一個類的這個私有成員,這就允許子類賦值給父類:
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();?//?不能將類型“Other”分配給類型“Parent”。類型具有私有屬性“age”的單獨(dú)聲明
當(dāng)指定 other
為 Parent
類類型,給 other
賦值 Other
創(chuàng)建的實(shí)例的時候,會報錯。因?yàn)?nbsp;Parent
的 age
屬性是私有成員,外面是無法訪問到的,所以會類型不兼容。而children
的類型我們指定為了Parent
類類型,然后給它賦值為Children
類的實(shí)例,沒有問題,是因?yàn)?code>Children類繼承Parent
類,且實(shí)例屬性沒有差異,Parent
類有私有屬性age
,但是因?yàn)?code>Children類繼承了Parent
類,所以可以賦值。
同樣,使用 protected
受保護(hù)修飾符修飾的屬性,也是一樣的:
class?Parent?{ ??protected?age:?number; ??constructor()?{} } class?Children?extends?Parent?{ ??constructor()?{ ????super(); ??} } class?Other?{ ??protected?age:?number; ??constructor()?{} } const?children:?Parent?=?new?Children(); const?other:?Parent?=?new?Other();?//?不能將類型“Other”分配給類型“Parent”。屬性“age”受保護(hù),但類型“Other”并不是從“Parent”派生的類
6. 泛型類型兼容性
泛型中包含類型參數(shù),這個類型參數(shù)可能是任何類型,使用時類型參數(shù)會被指定為特定的類型,而這個類型只影響使用了類型參數(shù)的部分:
interface?Data<T>?{} let?data1:?Data<number>; let?data2:?Data<string>; data1?=?data2;?//??
data1
和 data2
都是 Data 接口的實(shí)現(xiàn),但是指定的泛型參數(shù)的類型不同,TS 是結(jié)構(gòu)性類型系統(tǒng),所以上面將 data2
賦值給 data1
是兼容的,因?yàn)?nbsp;data2
指定了類型參數(shù)為 string
類型,但是接口里沒有用到參數(shù) T
,所以傳入 string
類型還是傳入 number
類型并沒有影響。
再來看個例子:
interface?Data<T>?{ ??data:?T; } let?data1:?Data<number>; let?data2:?Data<string>; data1?=?data2;?//?不能將類型“Data<string>”分配給類型“Data<number>”。不能將類型“string”分配給類型“number”
現(xiàn)在結(jié)果就不一樣了,賦值時報錯,因?yàn)?nbsp;data1
和 data2
傳入的泛型參數(shù)類型不同,生成的結(jié)果結(jié)構(gòu)是不兼容的。
以上就是TypeScript中類型兼容性的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于TypeScript類型兼容性的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js實(shí)現(xiàn)日期顯示的一些操作(實(shí)例講解)
下面小編就為大家?guī)硪黄猨s實(shí)現(xiàn)日期顯示的一些操作(實(shí)例講解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07詳解JavaScript中if語句優(yōu)化和部分語法糖小技巧推薦
在前端日常開發(fā)過程中,if?else判斷語句使用的次數(shù)應(yīng)該是比較頻繁的了,一些較為復(fù)雜的場景,可能會用到很多判斷,本文給大家介紹JavaScript中if語句優(yōu)化和部分語法糖小技巧,感興趣的朋友一起看看吧2022-05-05JS實(shí)現(xiàn)的簡單標(biāo)簽點(diǎn)擊切換功能示例
這篇文章主要介紹了JS實(shí)現(xiàn)的簡單標(biāo)簽點(diǎn)擊切換功能,涉及javascript事件響應(yīng)及頁面元素遍歷、屬性動態(tài)變換等相關(guān)操作技巧,需要的朋友可以參考下2017-09-09JavaScript基于面向?qū)ο髮?shí)現(xiàn)的無縫滾動輪播示例
這篇文章主要介紹了JavaScript基于面向?qū)ο髮?shí)現(xiàn)的無縫滾動輪播,結(jié)合實(shí)例形式分析了JavaScript面向?qū)ο髮?shí)現(xiàn)的無縫滾動輪播相關(guān)對象定義、初始化及功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-01-01JScript中使用ADODB.Stream判斷文件編碼的代碼
在實(shí)現(xiàn)TextStraem的時候,找到判斷文件編碼的代碼是VBS的,但是在JScript中是沒有ASC等函數(shù)的,也不能對二進(jìn)制數(shù)據(jù)進(jìn)行處理,因此需要通過一個特別的方法來獲取文件開關(guān)的編碼標(biāo)識。2008-06-06