TS 中的類型推斷與放寬實(shí)例詳解
簡(jiǎn)介
我們知道在編碼時(shí)即使不標(biāo)注變量類型,TypeScript 編譯器也能推斷出變量類型,那 TypeScript 編譯器是怎么進(jìn)行類型推斷,在類型推斷時(shí)又是如何判斷兼容性的呢?
此文,正好為你解開(kāi)這個(gè)疑惑的,掌握本文講解的類型推斷與類型放寬知識(shí)點(diǎn)后將對(duì) TypeScript 的類型系統(tǒng)有更深的認(rèn)識(shí)。
不妨先看看下面幾個(gè)問(wèn)題,如果你都能回答上,那么可以不用閱讀此文了。
- 這里變量 x 和 y 分別為什么類型,為什么?
let x = 0; const y = 0;
- 這里函數(shù)返回值、變量 x 為什么類型,為什么?
function f() { return 0 } let x = f();
- 這里 list 為什么類型,為什么?
const list = ['hello', 0];
- 這里 x、y、a、b 為什么類型,為什么?
const x = 0; let y = x; const a: 0 = 0; let b = a;
類型推斷與放寬概念
我們知道 JS 中表達(dá)式都具有返回值,在 TypeScript 程序中表達(dá)式也一樣具有返回值的同時(shí)還具有一種類型(返回值的類型),且此類型來(lái)源分為:類型注解、類型推斷。
類型注解是通過(guò)編寫代碼手動(dòng)指定表達(dá)式返回值的類型,如下代碼:
let x: number = 0; // 通過(guò)類型注解指定變量 x 為 number 類型
類型推斷指的是 TypeScript 編譯器自動(dòng)推測(cè)表達(dá)式返回值的類型,是一種比較智能的類型推測(cè)方法,可以簡(jiǎn)化代碼,如下代碼:
let x = 0; // 這里 TypeScript 編譯器自動(dòng)推斷變量 x 為 number 類型
上面兩段代碼中字面量 Literal
的值明明是字面量類型 0
,但是變量 x
卻變?yōu)榱?number
類型。值的類型和推斷的變量類型不一致,這就涉及到 TypeScript 的類型放寬了。
常規(guī)類型推斷
上述代碼定義了變量 x 并給其賦值了初始值,屬于常規(guī)類型推斷。
下面代碼中,變量 x 具有初始值 0,編譯器推斷其類型為 number
類型。
下面代碼中,變量 x 具有初始值 0,但是使用了 const
關(guān)鍵字定義其為常量,故編譯器推斷其類型為字面量類型 0
。
假如變量聲明時(shí)未指定初始值呢?這時(shí),編譯器將其自動(dòng)推斷為 any
類型。根據(jù)[[子類型兼容性]]章節(jié)中介紹可知,any
類型屬于頂端類型之一,不是任意類型的子類型,但是卻與任意類型滿足賦值兼容性,這樣未指定初始值的變量 x 后面可以被被賦值為任意類型。
最佳通用類型
編譯器在進(jìn)行類型推斷過(guò)程中,有可能推斷出多個(gè)可能得類型,并會(huì)參考所有可能的類型得出最終的最佳通用類型。
這里得出的類型可能為字面量 hello 對(duì)應(yīng)的原始類型 string、字面量 0 對(duì)應(yīng)的原始類型 number,得出的最佳通用類型為 string | number
。
const list = ['hello', 0]; // (string | number)[]
這里正好解釋了開(kāi)篇提出的問(wèn)題 3
當(dāng)數(shù)組的成員類型存在子類型關(guān)系時(shí),最佳通用類型也會(huì)有所不同。
這里 list1
根據(jù)可能的類型 A、B 得出最佳通用類型為 A | B
,list2
所有可能的類型有 A、B、Base,但是存在[[子類型兼容性]]: A <- Base
和 B <- Base
,所以得出的最佳通用類型為 Base
。
class Base { version: string = '1.0.0' } class A extends Base {} class B extends Base {} const list1 = [new A(), new B()] // (A | B)[] const list2 = [new A(), new B(), new Base()] // Base[]
代碼運(yùn)行驗(yàn)證如下:
按上下文歸類
上文說(shuō)的常規(guī)類型推斷、最佳通用類型都是由表達(dá)式的結(jié)果推導(dǎo)對(duì)應(yīng)變量的類型,這是一個(gè)由右向左的推斷過(guò)程。TypeScript 編譯器還能夠由變量的類型來(lái)推導(dǎo)變量對(duì)應(yīng)初始值的類型,這是一個(gè)由左向右的推斷過(guò)程。
這里指定變量 f 為 AddFunction
類型,給定的初始值是一個(gè)函數(shù),并且這個(gè)函數(shù)的形參和返回值都未指定類型,編譯器會(huì)自動(dòng)根據(jù) f 的類型推導(dǎo)出初始值的形參和返回值類型。
interface AddFunction { (x: number, y: number): number; } let f: AddFunction = (x, y) => { return x + y; }
編譯器按上下文歸類推斷出的類型如下:
類型放寬
上文在介紹最佳通用類型時(shí)提到過(guò)“字面量 hello 對(duì)應(yīng)的原始類型 string”,這就屬于類型放寬。編譯器在進(jìn)行類型推斷時(shí)候會(huì)進(jìn)行類型放寬,比如字面量類型 hello 放寬為原始類型 string。同樣,下面變量 x 也會(huì)被放寬為 number 類型。
let x = 0; // number
類型放寬分為:常規(guī)類型放寬、字面量類型放寬兩類,見(jiàn)下文。
常規(guī)類型放寬
undefined
和 null
類型會(huì)被編譯器放寬為 any 類型,不過(guò)這一特性在配置的編譯器檢查規(guī)則 --strictNullChecks
不同時(shí)情況不一樣。
非嚴(yán)格類型檢查模式
修改 tsconfig.json 配置文件為如下:
{ "compilerOptions": { "strictNullChecks": false } }
let x1 = undefined; // any const x2 = undefined; // any let y1 = null; // any const y2 = null; // any
此模式下,undefined 的值依然是 undefined 類型(null 同理),只是編譯器在進(jìn)行類型推斷時(shí)將 undefined 類型放寬為了 any 類型。
嚴(yán)格類型檢查模式
修改 tsconfig.json 配置文件為如下:
{ "compilerOptions": { "strictNullChecks": true } }
let x1 = undefined; // undefined const x2 = undefined; // undefined let y1 = null; // null const y2 = null; // null
此模式下,編譯器不會(huì)對(duì) undefined、null 類型進(jìn)行放寬,undefined 的值依然是 undefined 類型(null 同理)。
字面量類型放寬
字面量類型在進(jìn)行類型推斷時(shí),若當(dāng)前表達(dá)式的值是可變的,則會(huì)對(duì)字面量的類型進(jìn)行放寬,放寬規(guī)則如下表。
開(kāi)篇的問(wèn)題 1 中的代碼見(jiàn)下方,定義了兩個(gè)表達(dá)式,之前 let 定義的表達(dá)式值是可變的,const 定義的表達(dá)式值是不可變的。因此,變量 x 類型按照字面量進(jìn)行放寬為 string 類型,變量 y 類型不會(huì)進(jìn)行放寬,為字面量類型 0。
let x = 0; const y = 0;
對(duì)象、數(shù)組字面量類型的放寬
上文以表達(dá)式的值是否可變的角度來(lái)看待字面量類型是否可以放寬并非十分恰當(dāng),對(duì)于使用 const 關(guān)鍵字定義的對(duì)象、數(shù)組的情況則稍有不同。
JS 中 const 定義的變量不可變指的是變量指向的指針不可變,但是對(duì)象、數(shù)組是引用類型,當(dāng)對(duì)象的屬性或數(shù)組的元素的值變化(或者指向的指針變化)時(shí),該變量的指針并未改變。
因此,對(duì)象、數(shù)組字面量類型在進(jìn)行推斷時(shí)也會(huì)進(jìn)行類型放寬,這正是開(kāi)篇的問(wèn)題 3 的解答。
下面代碼 base.version
的類型會(huì)進(jìn)行放寬,結(jié)果類型為:number,base.author
同樣,放寬為:string。
const base = { version: 1, author: 'JohnieXu' };
下面代碼 list 的類型會(huì)進(jìn)行放寬,結(jié)果類型為:(string | number)[]
。
const list = ['hello', 0];
類字面量類型的放寬
類字面量和對(duì)象字面量比較相似,因?yàn)樵陬愒?JS 中(或者說(shuō) JS 解釋器)也是通過(guò)對(duì)象進(jìn)行模擬的,不同僅在于類的屬性具有修飾符。對(duì)于具有 readonly 修飾符的對(duì)象屬性,因其值不可變,故不會(huì)進(jìn)行類型放寬。
函數(shù)返回值字面量類型的放寬
在函數(shù)或方法中,若返回值的類型為字面量類型,則編譯器推斷的返回值類型會(huì)放寬;若返回值的類型為字面量聯(lián)合類型,則不會(huì)放寬。
TS 內(nèi)部類型放寬規(guī)則
每個(gè)字面量類型都有一個(gè)內(nèi)置屬性表示其是否可以被放寬,而 TypeScript 編譯器會(huì)根據(jù)放寬規(guī)則來(lái)推斷出這個(gè)內(nèi)置屬性。
在 TypeScript 語(yǔ)言內(nèi)部實(shí)現(xiàn)中,根據(jù)字面量的來(lái)源不同進(jìn)行了分類,來(lái)自于表達(dá)式的字面量類型標(biāo)記為全新的(fresh)字面量類型。只有全新的字面量類型才是可放寬的字面量類型,并且根據(jù)字面量處于表達(dá)式的位置,分為:可變值位置、不可變值位置。
因此,字面量的類型可放寬的充分必要條件為:為全新的字面量類型,且在代碼中處于可變值的位置。
實(shí)例分析
以開(kāi)篇的問(wèn)題 4 中部分代碼為例:
const x = 0; let y = x;
變量 x、y 的類型見(jiàn)下圖,可見(jiàn)兩者類型并不相同,x 類型未放寬,y 類型有放寬。
分析過(guò)程如下:
- 分析表達(dá)式
const x = 0;
- 表達(dá)式中字面量 0 為全新的字面量類型
- 表達(dá)式中使用了 const 關(guān)鍵字,字面量 0 處于不可變值位置,因此推斷 x 類型時(shí)不進(jìn)行類型放寬
- 變量 x 的類型是:可放寬的數(shù)字字面量類型 0(全新的字面量類型 0)
- 分析表達(dá)式
let y = x;
- 表達(dá)式中變量 x 為可放寬的數(shù)字字面量類型 0
- 表達(dá)式中使用了 let 關(guān)鍵字,變量 x 處于可變值位置,因此推斷 y 類型時(shí)進(jìn)行類型放寬
- 變量 y 的類型是可放寬的數(shù)字字面量類型 0 的放寬類型,即:number 類型。
下面還是以開(kāi)篇的問(wèn)題 4 中部分代碼為例(說(shuō)明使用了類型注解的場(chǎng)景):
const a: 0 = 0; let b = a;
變量 a、b 的類型見(jiàn)下圖,可見(jiàn)兩者類型相同,都沒(méi)有類型放寬。
分析過(guò)程如下:
- 分析表達(dá)式
const a: 0 = 0;
- 變量 a 的初始值 0 的類型為全新的字面量類型 0,即可放寬的字面量類型 0
- 但是,這里通過(guò)類型注解 0,指定了變量 a 的類型為字面量類型 0,由于類型注解的字面量類型不是全新的字面量類型,所以變量 a 的類型為不可放寬的字面量類型 0
- 分析表達(dá)式
let b = a;
- 這里變量 b 的初始值 a 的類型為不可放寬的字面量類型 0,雖然使用 let 關(guān)鍵字定義讓其處于可變值位置,但是不滿足類型放寬的必要條件,所以變量 b 的類型為不可放寬的字面量類型 0。
開(kāi)篇問(wèn)題解答
開(kāi)篇提出的問(wèn)題中 1、3、4 已在上文講解過(guò)程中進(jìn)行過(guò)分析,這里分析一下問(wèn)題 2 。
function f() { return 0 } let x = f();
先看這個(gè)問(wèn)題的答案,如下:
分析過(guò)程:
- 函數(shù) f 的返回值類型為字面量類型 0,根據(jù)上文介紹的“函數(shù)返回值類型為字面量類型會(huì)進(jìn)行類型放寬”可知,函數(shù) f 返回值類型為字面量類型 0 放寬的結(jié)果類型:number 類型
- 分析表達(dá)式
let x = f();
- 這里變量 x 的初始值是函數(shù) f 的返回值,是 number 類型
- 表達(dá)式采用了 let 關(guān)鍵字,處于可變值位置,會(huì)對(duì) number 類型進(jìn)行放寬
- number 類型放寬的結(jié)果類型為自身:number 類型,故變量 x 為 number 類型。
以上就是TS 中的類型推斷與放寬實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于TS類型推斷與放寬的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用typeScript 進(jìn)行扁平化數(shù)據(jù)轉(zhuǎn)樹(shù)實(shí)現(xiàn)demo
這篇文章主要介紹了使用typeScript 進(jìn)行扁平化數(shù)據(jù)轉(zhuǎn)樹(shù)實(shí)現(xiàn)demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06聯(lián)合類型Union?Types與交叉類型Intersection?Types區(qū)別解析
這篇文章主要為大家介紹了聯(lián)合類型Union?Types與交叉類型Intersection?Types區(qū)別詳解2023-06-06TypeScript數(shù)組實(shí)現(xiàn)棧與對(duì)象實(shí)現(xiàn)棧的區(qū)別詳解
這篇文章主要為大家介紹了TypeScript數(shù)組實(shí)現(xiàn)棧與對(duì)象實(shí)現(xiàn)棧的區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09移動(dòng)設(shè)備web開(kāi)發(fā)首選框架:zeptojs介紹
這篇文章主要介紹了移動(dòng)設(shè)備web開(kāi)發(fā)首選框架:zeptojs介紹,他兼容jquery的API,所以學(xué)起來(lái)或用起來(lái)并不吃力,需要的朋友可以參考下2015-01-01FastAdmin表單驗(yàn)證data-rule插件—Nice-validator的使用方法
FastAdmin的表單驗(yàn)證data-rule非常方便,也很炫酷,采用的Nice-validator是一款非常強(qiáng)大的表單驗(yàn)證插件,通過(guò)簡(jiǎn)單在元素上配置規(guī)則,即可達(dá)到驗(yàn)證的效果,怎么使用Nice-validator插件呢2023-09-09基于Javascript實(shí)現(xiàn)頁(yè)面商品個(gè)數(shù)增減功能
本文給大家介紹基于Javascript實(shí)現(xiàn)頁(yè)面商品個(gè)數(shù)增減功能,通過(guò)點(diǎn)擊數(shù)量增減個(gè)數(shù),代碼分為前端頁(yè)面,后臺(tái)返回代碼,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2019-07-07