TypeScript?背后的結(jié)構(gòu)化類型系統(tǒng)原理詳解
前言
你能說清楚類型、類型系統(tǒng)、類型檢查這三個的區(qū)別嗎?在理解TypeScript的結(jié)構(gòu)化類型系統(tǒng)之前,我們首先要搞清楚這三個概念和它們之間的關(guān)系
- 類型:即對變量的訪問限制與賦值限制。如 TypeScript 中的原始類型、對象類型、函數(shù)類型和字面量類型等類型,當一個變量類型確定后,你不能訪問這個類型中不存在的屬性或方法,也不能將其他不兼容的類型的變量賦值給該變量
- 類型系統(tǒng):本質(zhì)上是一組規(guī)則,其描述如何為變量、函數(shù)等結(jié)構(gòu)分配和實施類型。同時還定義了如何判斷類型之間的兼容性,也正是我們今天主要討論的概念
- 類型檢查:是一種能力,一種確保類型遵循類型系統(tǒng)下的類型兼容性的能力
理解它們對理解我們今天要討論的TypeScript的結(jié)構(gòu)化類型系統(tǒng)很有幫助
類型系統(tǒng)分為結(jié)構(gòu)化類型系統(tǒng)和標稱類型系統(tǒng),首先我們來看看它們分別都是什么
什么是結(jié)構(gòu)化類型系統(tǒng)?
基于類型結(jié)構(gòu)進行類型兼容性判斷
關(guān)鍵體現(xiàn)在兩個類型的比較當中,當兩個類型比較時,如果是按照屬性和方法是否相同來比較的話就稱為結(jié)構(gòu)化類型系統(tǒng),也叫鴨子類型。
比如下面這個例子:
class Dog { say() { console.log('wang wang wang!') } } class Cat { say() { console.log('miao miao miao!') } } const invokeSay = (dog: Dog) => { dog.say() } const dog = new Dog() const cat = new Cat() invokeSay(dog) // wang wang wang! invokeSay(cat) // miao miao miao!
雖然invokeSay函數(shù)接收的參數(shù)類型是Dog,但是由于Cat類型的結(jié)構(gòu)和Dog是一樣的(都是只有一個 say 方法),因此會被認為是同一種類型,這就是結(jié)構(gòu)化類型的特點,基于類型結(jié)構(gòu)進行類型兼容性判斷
代表語言:C#、Python、Objective-C
什么是標稱類型系統(tǒng)?
基于類型名進行兼容性判斷
與結(jié)構(gòu)化類型系統(tǒng)相對的叫標稱類型系統(tǒng),它在判斷兩個類型是否相同時,只看它們的名稱是否相同,即便內(nèi)部結(jié)構(gòu)完全相同也不能認為是同一種類型,比如下面這個例子:
/** @description 人民幣 */ type CNY = number /** @description 美元 */ type USD = number const CNYCount: CNY = 666 const USDCount: USD = 333 const addCount = (source: CNY, input: CNY) => source + input addCount(CNYCount, USDCount)
在標稱類型系統(tǒng)中,這里對于addCount的調(diào)用是錯誤的,盡管CNY和USD類型都是number類型,但由于它們的名稱不同,因此被視為是不同類型
代表語言:C++、Java、Rust
結(jié)構(gòu)化類型系統(tǒng)等價于鴨子類型系統(tǒng)嗎?
嚴格意義上兩者并不等同
- 結(jié)構(gòu)化類型系統(tǒng)基于完全的類型結(jié)構(gòu)來判斷類型兼容性
- 鴨子類型系統(tǒng)基于運行時訪問的部分來判斷類型兼容性
TypeScript 本身并不是在運行時進行類型檢查,因此并不嚴格等價于鴨子類型系統(tǒng)
如何在 TypeScript 中模擬標稱類型系統(tǒng)?
由于 TypeScript 的類型系統(tǒng)是結(jié)構(gòu)化類型系統(tǒng),所以剛剛那個例子在 TypeScript 中是可以正常運行的:
/** @description 人民幣 */ type CNY = number /** @description 美元 */ type USD = number const CNYCount: CNY = 666 const USDCount: USD = 333 const addCount = (source: CNY, input: CNY) => source + input addCount(CNYCount, USDCount)
這里我們的意圖應(yīng)當是讓人民幣 CNY 和美元 USD 作為兩種不同的類型,但是由于 TypeScript 結(jié)構(gòu)化類型系統(tǒng)的特性,兩個類型本質(zhì)上都是number類型,因此會被認為是同一個類型
而如果是標稱類型系統(tǒng)就不會有這個問題,如果我們能在 TypeScript 中模擬實現(xiàn)標稱類型系統(tǒng)就符合預(yù)期了,那么要怎么模擬呢?
對比結(jié)構(gòu)化類型系統(tǒng)和標稱類型系統(tǒng)的特點,只要我們能夠在兩個完全兼容的結(jié)構(gòu)化類型中加入一個標識符,那么即便這兩個類型的結(jié)構(gòu)是兼容的,但由于這個標識符并不相同,因而會被認為是兩個不同的類型
利用這個特點我們就可以在 TypeScript 中模擬標稱類型系統(tǒng)
以下有兩種方式模擬實現(xiàn)標稱類型系統(tǒng):
- 通過交叉類型實現(xiàn) -- 只能在類型層面上實現(xiàn),無法在運行時邏輯上實現(xiàn)
- 通過類實現(xiàn) -- 能兼顧類型層面和運行時邏輯層面
交叉類型實現(xiàn)
我們可以實現(xiàn)一個工具類型Nominal,對傳入的泛型參數(shù)進行處理,將其和一個特殊的類型進行交叉類型操作,上面提到的標識符就是由這個特殊的類型提供的
nominal.ts
class TagProtector<T extends string> { protected __tag__: T } export type Nominal<T, U extends string> = T & TagProtector<U> index.ts import { Nominal } from './nominal' /** @description 人民幣 */ type CNY = Nominal<number, 'CNY'> /** @description 美元 */ type USD = Nominal<number, 'USD'> const CNYCount = 666 as CNY const USDCount = 333 as USD const addCount = (source: CNY, input: CNY) => source + input // 類型“USD”的參數(shù)不能賦給類型“CNY”的參數(shù)。 // 不能將類型“USD”分配給類型“TagProtector<"CNY">”。 // 屬性“__tag__”的類型不兼容。 // 不能將類型“"USD"”分配給類型“"CNY"”。ts(2345) addCount(CNYCount, USDCount)
由于 TypeScript 實際運行時還是以 JavaScript 的方式運行的,所以類型代碼會被抹除,抹除類型后這個代碼仍然能夠正常執(zhí)行
這是因為通過這種方式模擬實現(xiàn)標稱類型系統(tǒng)只能在類型層面模擬,實際的運行時并不能起到檢查的作用,這時候就要用下面的方案 -- 用類模擬實現(xiàn)
類實現(xiàn)
/** @description 人民幣 */ class CNY { private __tag__!: void constructor(public value: number) {} } /** @description 美元 */ class USD { private __tag__!: void constructor(public value: number) {} } const CNYCount = new CNY(666) const USDCount = new USD(666) const addCount = (source: CNY, input: CNY) => source.value + input.value // 類型“USD”的參數(shù)不能賦給類型“CNY”的參數(shù)。 // 類型具有私有屬性“__tag__”的單獨聲明。 addCount(CNYCount, USDCount)
以上兩種方式本質(zhì)都是通過一個非公開的額外屬性對類型添加了額外的標識符,從而能夠讓結(jié)構(gòu)化類型系統(tǒng)將它們判斷為不同的類型
總結(jié)
相信現(xiàn)在你能夠理解什么是結(jié)構(gòu)化類型系統(tǒng)了,正如開頭介紹的類型系統(tǒng)的概念中所說,它定義了如何判斷類型之間的兼容性
而結(jié)構(gòu)化類型系統(tǒng)對于類型之間兼容性的判斷則是基于類型的結(jié)構(gòu)來判斷的,只要兩個類型的結(jié)構(gòu)上可兼容(如一個類型中的所有屬性和方法在另一個類型中都存在),則可以將兩個類型視為是兼容的
除此之外,我們還了解了與結(jié)構(gòu)化類型系統(tǒng)對應(yīng)的標稱類型系統(tǒng),并且了解到如何在TypeScript中模擬實現(xiàn)標稱類型系統(tǒng),讓我們對結(jié)構(gòu)化類型系統(tǒng)有更深刻的理解
以上就是TypeScript 背后的結(jié)構(gòu)化類型系統(tǒng)原理詳解的詳細內(nèi)容,更多關(guān)于TypeScript 結(jié)構(gòu)化類型系統(tǒng)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
setTimeout函數(shù)兼容各主流瀏覽器運行執(zhí)行效果實例
setTimeout是一個很不錯的函數(shù),網(wǎng)站頁面前端工程師經(jīng)常將其用于幾秒后執(zhí)行的動作,下文要講的setTimeout可以很好地兼容IE6,7,8,9以及谷歌等主流瀏覽器2013-06-06分享Javascript中最常用的55個經(jīng)典小技巧
這篇文章主要介紹了Javascript中最常用的55個經(jīng)典小技巧。需要的朋友可以過來參考下,希望對大家有所幫助2013-11-11Layui數(shù)據(jù)表格跳轉(zhuǎn)到指定頁的實現(xiàn)方法
今天小編就為大家分享一篇Layui數(shù)據(jù)表格跳轉(zhuǎn)到指定頁的實現(xiàn)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-09-09