【Typescript 類型檢查原理】類型守衛(wèi)是如何實現(xiàn)的
本文轉載自微信公眾號「神光的編程秘籍」,作者神說要有光zxg。轉載本文請聯(lián)系神光的編程秘籍公眾號。
這一節(jié)我們來理一下類型守衛(wèi)的實現(xiàn)原理,因為內容比較多,分為上下兩篇,上篇講實現(xiàn)思路,下篇是代碼實現(xiàn)。
什么是類型守衛(wèi)
javascript 的類型代表了一種可能性,表示可能占用的內存大小、可能調用的方法等。typescript 的類型包含了 javascript 的類型,并且對可以對類型做交集、并集、各種推導,最終產(chǎn)生準確的類型。typescript 的類型的推導也是一種可能性的推導,目標是得出的類型更準確的描述具體的變量類型。
精準就意味著要做一些類型的可能性的縮小,各種類型編程的目的都是產(chǎn)生更小更準確的類型,類型守衛(wèi)也是這個目的。
類型推導是使得整個類型變得更小更準確,而類型守衛(wèi)則是當類型進入某個分支的時候,暫時性的變得更小更精確,使得類型檢查更準確。
比如下面的代碼,整體類型是 string| number,這是一個聯(lián)合類型,當 a 進入 if 分支的時候,類型明顯只可能是 string,別的情況進不來,這時候可以做進一步的類型縮小,這就叫做類型守衛(wèi)。
- function func(a: string| number): string {
- if (typeof a === 'string') {
- return a.toLocaleLowerCase();
- } else {
- return a.toFixed(1);
- }
- }
類型守衛(wèi)的目的就是讓整體的類型在一些確定的條件下暫時性的變得更小更精確。這種條件包括 typeof、instanceOf、in、===、!==、==、!=。
為什么這些條件下可以縮小類型呢?因為能夠進入這些分支,那么變量顯然只可能是改種類型,所以類型的可能性自然可以做進一步的縮小。
比如 in 操作符觸發(fā)的類型守衛(wèi):
=== 判斷觸發(fā)的類型守衛(wèi):
同理 instanceof 等也是一樣,只要是進入能夠確定具體類型的分支,那么類型就可以做縮小。
在 ts 4.3 中,泛型的類型縮小也做了支持(之前只能通過類型斷言來縮小類型)。
類型縮小是自動的類型斷言,當有的時候類型縮小或者類型推導都不行的時候,就用 as 手動類型斷言。
實現(xiàn)思路分析
我們知道了類型縮小是在在進入條件分支的時候,對類型檢查用的類型做暫時性的縮小,那么實現(xiàn)的時候自然就是在 if、switch 的分支的檢查時,對類型做一些處理。
ts 的類型檢查是先通過解析配置文件的 includes、exclues、files 等,結合 lib、types、typeRoots 的配置來確定要做檢查的所有文件,然后對每個文件依次進行遞歸下降的類型檢查。
當檢查到 if、switch 的節(jié)點的時候,我們只需要判斷 test 部分是否是一個 BinaryExpression,并且 operator 是 in、===、!==、instanceOf 等情況。
根據(jù) operator 的不同分別做不同的判斷:
- in:判斷 left 是否是 right 變量的類型的一個屬性,如果是,對類型做縮小
- instanceof:判斷 left 的類型是否是 right 變量的類型的子類型,如果是,對類型做縮小
- === / !==: 分為包含 typeof 和不包含 typeof 兩種:
- 不包含 typeof:判斷 left 和 right 是否相等,如果是,把類型縮小到具體的字面量類型。
- 包含 typeof:如果兩邊有一邊是 typeof 的 UnaryExpression,則取類型之后再做比較,如果是,把類型縮小到具體的類型
總結
typscript 的高級類型的推導的目的就是縮小可能性范圍,讓類型更精確。有的時候,在進入一些分支的時候,類型就確定了,這時候就可以暫時性的對類型做范圍的縮小,這叫做類型守衛(wèi)。
觸發(fā)條件有 in、instanceof、typeof、===、!== 等能夠讓類型更準確的判斷。
類型守衛(wèi)相當于自動的類型斷言,當類型守衛(wèi)搞不定的時候,就手動用類型斷言 as 來縮小類型。
我們梳理了實現(xiàn)類型守衛(wèi)的思路,就是遇到條件語句 IfStatement、SwitchStatement 的時候,對 test 部分做判斷,如果包含 in、instanceof、typeof 等,做相應的類型處理,之后再進行類型檢查。