網站首頁 編程語言 正文
簡介
我們知道在編碼時即使不標注變量類型,TypeScript 編譯器也能推斷出變量類型,那 TypeScript 編譯器是怎么進行類型推斷,在類型推斷時又是如何判斷兼容性的呢?
此文,正好為你解開這個疑惑的,掌握本文講解的類型推斷與類型放寬知識點后將對 TypeScript 的類型系統有更深的認識。
不妨先看看下面幾個問題,如果你都能回答上,那么可以不用閱讀此文了。
- 這里變量 x 和 y 分別為什么類型,為什么?
let x = 0; const y = 0;
- 這里函數返回值、變量 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
卻變為了 number
類型。值的類型和推斷的變量類型不一致,這就涉及到 TypeScript 的類型放寬了。
常規類型推斷
上述代碼定義了變量 x 并給其賦值了初始值,屬于常規類型推斷。
下面代碼中,變量 x 具有初始值 0,編譯器推斷其類型為 number
類型。
下面代碼中,變量 x 具有初始值 0,但是使用了 const
關鍵字定義其為常量,故編譯器推斷其類型為字面量類型 0
。
假如變量聲明時未指定初始值呢?這時,編譯器將其自動推斷為 any
類型。根據[[子類型兼容性]]章節中介紹可知,any
類型屬于頂端類型之一,不是任意類型的子類型,但是卻與任意類型滿足賦值兼容性,這樣未指定初始值的變量 x 后面可以被被賦值為任意類型。
最佳通用類型
編譯器在進行類型推斷過程中,有可能推斷出多個可能得類型,并會參考所有可能的類型得出最終的最佳通用類型。
這里得出的類型可能為字面量 hello 對應的原始類型 string、字面量 0 對應的原始類型 number,得出的最佳通用類型為 string | number
。
const list = ['hello', 0]; // (string | number)[]
這里正好解釋了開篇提出的問題 3
當數組的成員類型存在子類型關系時,最佳通用類型也會有所不同。
這里 list1
根據可能的類型 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[]
代碼運行驗證如下:
按上下文歸類
上文說的常規類型推斷、最佳通用類型都是由表達式的結果推導對應變量的類型,這是一個由右向左的推斷過程。TypeScript 編譯器還能夠由變量的類型來推導變量對應初始值的類型,這是一個由左向右的推斷過程。
這里指定變量 f 為 AddFunction
類型,給定的初始值是一個函數,并且這個函數的形參和返回值都未指定類型,編譯器會自動根據 f 的類型推導出初始值的形參和返回值類型。
interface AddFunction { (x: number, y: number): number; } let f: AddFunction = (x, y) => { return x + y; }
編譯器按上下文歸類推斷出的類型如下:
類型放寬
上文在介紹最佳通用類型時提到過“字面量 hello 對應的原始類型 string”,這就屬于類型放寬。編譯器在進行類型推斷時候會進行類型放寬,比如字面量類型 hello 放寬為原始類型 string。同樣,下面變量 x 也會被放寬為 number 類型。
let x = 0; // number
類型放寬分為:常規類型放寬、字面量類型放寬兩類,見下文。
常規類型放寬
undefined
和 null
類型會被編譯器放寬為 any 類型,不過這一特性在配置的編譯器檢查規則 --strictNullChecks
不同時情況不一樣。
非嚴格類型檢查模式
修改 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 類型。
嚴格類型檢查模式
修改 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 同理)。
字面量類型放寬
字面量類型在進行類型推斷時,若當前表達式的值是可變的,則會對字面量的類型進行放寬,放寬規則如下表。
開篇的問題 1 中的代碼見下方,定義了兩個表達式,之前 let 定義的表達式值是可變的,const 定義的表達式值是不可變的。因此,變量 x 類型按照字面量進行放寬為 string 類型,變量 y 類型不會進行放寬,為字面量類型 0。
let x = 0; const y = 0;
對象、數組字面量類型的放寬
上文以表達式的值是否可變的角度來看待字面量類型是否可以放寬并非十分恰當,對于使用 const 關鍵字定義的對象、數組的情況則稍有不同。
JS 中 const 定義的變量不可變指的是變量指向的指針不可變,但是對象、數組是引用類型,當對象的屬性或數組的元素的值變化(或者指向的指針變化)時,該變量的指針并未改變。
因此,對象、數組字面量類型在進行推斷時也會進行類型放寬,這正是開篇的問題 3 的解答。
下面代碼 base.version
的類型會進行放寬,結果類型為:number,base.author
同樣,放寬為:string。
const base = { version: 1, author: 'JohnieXu' };
下面代碼 list 的類型會進行放寬,結果類型為:(string | number)[]
。
const list = ['hello', 0];
類字面量類型的放寬
類字面量和對象字面量比較相似,因為在類在 JS 中(或者說 JS 解釋器)也是通過對象進行模擬的,不同僅在于類的屬性具有修飾符。對于具有 readonly 修飾符的對象屬性,因其值不可變,故不會進行類型放寬。
函數返回值字面量類型的放寬
在函數或方法中,若返回值的類型為字面量類型,則編譯器推斷的返回值類型會放寬;若返回值的類型為字面量聯合類型,則不會放寬。
TS 內部類型放寬規則
每個字面量類型都有一個內置屬性表示其是否可以被放寬,而 TypeScript 編譯器會根據放寬規則來推斷出這個內置屬性。
在 TypeScript 語言內部實現中,根據字面量的來源不同進行了分類,來自于表達式的字面量類型標記為全新的(fresh)字面量類型。只有全新的字面量類型才是可放寬的字面量類型,并且根據字面量處于表達式的位置,分為:可變值位置、不可變值位置。
因此,字面量的類型可放寬的充分必要條件為:為全新的字面量類型,且在代碼中處于可變值的位置。
實例分析
以開篇的問題 4 中部分代碼為例:
const x = 0; let y = x;
變量 x、y 的類型見下圖,可見兩者類型并不相同,x 類型未放寬,y 類型有放寬。
分析過程如下:
- 分析表達式
const x = 0;
- 表達式中字面量 0 為全新的字面量類型
- 表達式中使用了 const 關鍵字,字面量 0 處于不可變值位置,因此推斷 x 類型時不進行類型放寬
- 變量 x 的類型是:可放寬的數字字面量類型 0(全新的字面量類型 0)
- 分析表達式
let y = x;
- 表達式中變量 x 為可放寬的數字字面量類型 0
- 表達式中使用了 let 關鍵字,變量 x 處于可變值位置,因此推斷 y 類型時進行類型放寬
- 變量 y 的類型是可放寬的數字字面量類型 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 關鍵字定義讓其處于可變值位置,但是不滿足類型放寬的必要條件,所以變量 b 的類型為不可放寬的字面量類型 0。
開篇問題解答
開篇提出的問題中 1、3、4 已在上文講解過程中進行過分析,這里分析一下問題 2 。
function f() { return 0 } let x = f();
先看這個問題的答案,如下:
分析過程:
- 函數 f 的返回值類型為字面量類型 0,根據上文介紹的“函數返回值類型為字面量類型會進行類型放寬”可知,函數 f 返回值類型為字面量類型 0 放寬的結果類型:number 類型
- 分析表達式
let x = f();
- 這里變量 x 的初始值是函數 f 的返回值,是 number 類型
- 表達式采用了 let 關鍵字,處于可變值位置,會對 number 類型進行放寬
- number 類型放寬的結果類型為自身:number 類型,故變量 x 為 number 類型。
原文鏈接:https://juejin.cn/post/7151647959938957343
相關推薦
- 2022-11-15 Python函數式編程之返回函數實例詳解_python
- 2022-10-14 el-tree 懶加載,默認加載N級.異步加載子節點
- 2022-11-22 Python網絡請求模塊urllib與requests使用介紹_python
- 2022-08-20 Docker容器修改端口映射的實現_docker
- 2022-04-27 jQuery實現移動端懸浮拖動效果_jquery
- 2022-12-09 C++中使用cout以hex格式輸出方式_C 語言
- 2023-01-20 python使用paramiko執行服務器腳本并拿到實時結果_python
- 2022-06-07 FreeRTOS操作系統的配置示例解析_操作系統
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支