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