【最全分析總結3W字】我是怎樣從0開始學會TypeScript的
一邊學習,一邊記錄,前前后后花了至少2個月的時間,算是把TS大部分都搞明白了。
這篇文章的篇幅有點長,是我本人學習過程中的一些記錄,參考了很多優秀博主的一些文章,以及在B站看了一些TS的視頻,把幾乎所有TS涵蓋到的基礎知識點都總結了下來,所以,對于想學習TS的小伙伴下來,一定一定要認認真真把這篇文章看完。
3萬字的教程,不敢說是全網最好,但可以說是全網最詳細。
對于新手入門來說是一篇非常不錯的寶藏文章,幾乎每個 TypeScript 的知識點都有詳細的講到,并且附上一些簡單的示例,通俗易懂,希望可以給想學習 TS 的小伙伴帶來動力!
一、了解TypeScript1. 什么是TypeScript- TypeScript是由微軟開發的一門開源的編程語言。
- TypeScript,簡稱TS,是JavaScript的超集(以JavaScript為基礎構建的語言,JS有的TS都有)。
- Typescript = Type + JavaScript(在JS基礎之上,為JS添加了類型支持)。
- 可以在任何支持JavaScript的平臺中執行。
我們都知道,JavaScript是弱類型的編程語言,很多的錯誤只有在運行的時候才會被發現,而TS在代碼編譯的時候(代碼執行前)就可以發現錯誤。
3. TypeScript的特點- 支持最新的ECMAScript語法
- 在代碼編譯階段就能發現錯誤
- 在JS基礎上增加了類型支持
TypeScript | JavaScript |
編譯期發現錯誤 | 運行時發現錯誤 |
強類型語言,支持靜態和動態類型 | 弱類型語言,沒有靜態類型選項 |
支持模塊、泛型和接口 | 不支持模塊、泛型和接口 |
代碼運行時會被編譯成JavaScript代碼,瀏覽器才能識別 | 可以直接在瀏覽器使用 |
npm i -g typescript
2. 驗證TS是否安裝成功tsc -v
3. TypeScript初體驗- 創建一個TS文件,hello.ts(注意:TS文件的后綴名為 .ts),并輸入以下的內容
- 將TS文件編譯為JS文件,在終端中輸入命令:tsc hello.ts, (此時,在同級目錄中會出現一個同名的JS文件)
- 執行JS代碼:在終端中輸入命令,node hello.js,終端會輸出 hello, Echo。
每次修改代碼后,都要重復執行兩個命令,才能運行TS代碼,我們可以直接使用ts-node工具包,直接在node.js中執行TS代碼。
安裝命令:npm i -g ts-node
使用方式:ts-node hello.ts
5. 運行TS文件的另一種方法在VSCode中安裝Code Runner擴展插件,在需要運行的ts文件中按鼠標右鍵,選擇Run Code(快捷鍵:Ctrl+Alt+N)。
對于剛入門TypeScript的小伙伴來說,我們可以不用安裝本地的運行環境,而是直接使用線上的 TypeScript Playground,我們就可以在瀏覽器中學習和編寫TypeScript代碼,通過配置TS Config的Target,可以設置不同的編譯目標,從而編譯生成不同的目標代碼。
三、TypeScript類型注解1. 類型注解作用TS類型注解的作用是為變量、函數、類等添加類型信息,用于在靜態類型檢查階段檢查代碼的類型正確性。
2. 類型注解用途- 提供類型提示:類型注解使得開發人員可以清晰地知道變量的類型,編輯器能夠根據類型注解給出相應的代碼提示,提高代碼的可讀性和可維護性。
- 靜態類型檢查:通過給變量添加類型注解,在編譯階段可以對代碼進行靜態類型檢查。它會檢查變量的類型是否符合預期的類型,并發現潛在的類型錯誤。
- 函數參數類型檢查:類型注解可以幫助開發人員在編寫函數時明確參數的類型,并在調用函數時進行參數類型檢查。這樣可以避免因參數類型不匹配引發的潛在錯誤。
- 對象屬性類型約束:通過類型注解,可以約束對象的屬性類型,確保對象的屬性符合特定的類型要求。
例如,上述代碼中的 : number 就是類型注解。 約定變量num的類型為number(數值類型)。
3. 類型注解注意事項- 約定了什么類型,就只能給變量賦值該類型的值,否則,就會報錯。
例如,我們將變量num的值123,重新賦值為字符串的“456”,此時我們就可以看到編輯器的錯誤提示:不能將類型“string”分配給類型“number”。
- 類型注解只在編譯階段起作用,并不會影響運行時的行為。 在編譯后的 JavaScript 代碼中,類型注解會被編譯器忽略。
我們可以將TS中常用的基礎類型分為兩類:
- JS已有的類型
- TS新增的類型
JS已有的類型,我們又可以分為兩類:
- 原始數據類型:number、string、boolean、null、undefined、symbol(ES6中的新類型)、bigint(ES10中的新類型)。
- 對象類型:object(包括數組、對象、函數等對象)。
TS新增的類型:any、void、自定義類型(類型別名)、聯合類型、接口、元組、字面量類型、枚舉等。
1.1. 數值(number)和JS一樣,TS里的所有數字都是浮點數。 這些浮點數的類型是 number。 除了支持十進制和十六進制字面量,TS還支持ECMAScript 2015中引入的二進制和八進制字面量。
在TS中,使用 number ****來定義數值類型:
編譯結果:
在TS中,使用 boolean 來定義布爾值類型:
編譯結果:
在TS中,使用 string 來定義字符串類型:
在TS中,字符串的表現形式主要有以下三種方式:
- 使用單引號( ' )
- 使用雙引號( " )
- 使用模板字符串,它可以定義多行文本和內嵌表達式。這種字符串是被反引號包圍( ` ),并且以 ${ expr } 這種形式嵌入表達式
編譯結果:
null 和 undefined 是所有類型的子類型,默認情況下,可以把null 和 undefined賦值給其他類型。
注意:如果你將 tsconfig.json 文件中的 strictNullChecks 選項設置為 false,下面這種操作不會報錯,不過盡量不要這么做。
編譯結果:
注意:如果你在 tsconfig.json 文件中指定了“strictNullChecks:true”,null 和 undefined 只能賦值給 void 和它們各自的類型。
下面這種情況會報錯:
symbol 是ES6新增的一種基本數據類型,Symbol()函數 會返回 symbol 類型的值,每個從 Symbol()函數 返回的 symbol 的值都是唯一的。
上面的代碼創建了三個新的 symbol 類型,但是注意的是,每個從 Symbol()函數 返回的值都是唯一的。
此時,如果我們在控制臺打印下面的代碼,兩者并不相等。
bigint 是ES10新增的一種基本數據類型,在JS中,可以用 Number 表示的最大整數為 2^53 - 1,可以寫為 Number.MAX_SAFE_INTEGER。如果超過了這個界限,那么就可以用 BigInt 來表示,它可以表示任意大的整數。
在一個整數字面量后面加 n 的方式定義一個 bigint,或者調用函數 BigInt()。
- 在JS中,null 表示“什么都沒有”,而 undefined 是一個沒有設置值的變量
- 用 typeof 檢測 null,返回 object;typeof 一個沒有值的變量會返回 undefined
- null 是一個只有一個值的特殊類型,表示一個空對象的引用
- null 和 undefined 是其它任何類型(包括void)的子類型,可以賦值給其它類型,如數字類型,此時,賦值后的類型會變成 null 或 undefined。而在TS中啟用嚴格的空校驗(strictNullChecks)特性,就可以使得 null 和 undefined 只能被賦值給 void 或本身對應的類型
- number 和 bigint 都可以表示數字,但是兩者不能進行相互轉換
- 僅在值大于 2^53 - 1時,才使用 bigint,否則盡量使用 number
- 用 typeof 檢測 bigint 對象時,返回 bigint,用 typeof 檢測 number,返回 number
數組類型的寫法有兩種:
- 在類型后面加上 [],例如 number[]
- 使用數組泛型 <>,例如 Array
推薦使用第一種寫法。
注意:
- 如果我們定義了一個number類型的數組,此時數組的項中就不能出現其它的類型。
- 如果我們希望數組中既有number類型,又有string類型,此時我們可以用聯合類型來寫(關于聯合類型,后面會詳細講到)。
上面的代碼,表示的是,定義一個arr數組,這個數組中可以出現 number 或者 string 類型的元素。
上面的代碼,arr1 和 arr2 都表示即可以是number類型,又可以是string[],加了小括號和不加小括號,含義不同。
1.8.2. 函數類型函數類型實際上指的是:函數參數和返回值的類型。
為函數指定類型的兩種方式:
- 單獨指定參數、返回值的類型
- 同時指定參數、返回值的類型
在JS中,有兩種常見的定義函數的方式:
- 函數聲明
- 函數表達式
注意:不要把ES6中的 => 和 TypeScript 中的 =>混淆了。
在ES6中,=>叫做箭頭函數。而在 TypeScript 的類型定義中,=>用來表示函數的定義,左邊是輸入類型,需要用括號括起來,右邊是輸出類型。
1.8.2.3. 函數沒有返回值如果函數沒有返回值,那么,函數返回值類型為:void。
使用函數實現某個功能時,參數可以傳也可以不傳,這種情況下,在給函數參數指定類型時,就用到可選參數了。
可選參數使用問號( ? )標記,表示該參數可以省略。
上面的代碼中,我們在第二個參數 greeting 的后面加了個問號,表示在調用 greet() 函數時,該參數可傳可不傳。
注意:可選參數只能出現在參數列表的最后面,也就是說,可選參數后面不能再出現必選參數。
錯誤演示:下面代碼中,我們把第一個參數改為可選的,第二個參數改為必選的,然后將鼠標移到必選參數上面,可以看到錯誤提示:“必選參數不能位于可選參數后”。
在ES6中,允許給函數的參數添加默認值,而TypeScript會將添加了默認值的參數識別為可選參數。
默認參數使用等號(=)賦予默認值。
注意:與可選參數不同的是,帶默認值的參數不需要放在必選參數的后面。如果帶默認值的參數出現在必選參數的前面,我們在調用函數時,必須明確的傳入 undefined 值來獲得默認值。
使用三個點( ... )前綴和參數名來定義剩余參數。
剩余參數允許我們將不確定數量的參數表示為一個數組。
注意:剩余參數必須是函數參數列表中的最后一個參數。
1.8.2.7. 函數重載函數重載允許我們為同一個函數提供多個函數類型定義,以便在不同的參數類型或返回值類型下進行不同的處理。
例如,我們現在需要實現一個函數,需求是:輸入數字123,輸出反轉的數字321,輸入字符串"hello",輸出反轉的字符串"olleh"。
利用聯合類型,我們可以這么實現:
然后這樣會有一個問題,就是輸出的類型不能準確的知道,我們想輸入為數字的時候,輸出的類型應該也為數值類型,輸入為字符串的時候,輸出類型應該也為字符串類型。
這時,我們可以用重載定義多個reserve的函數類型:
上述代碼中,第1-2行是函數定義,第3-9行是函數實現。第11行代碼,我們調用reverse函數,并傳入數值123,使用typeof檢測類型為number,第12行代碼,我們調用reverse函數,并傳入字符串"hello",使用typeof檢測類型為string,這樣我們利用函數重載就實現了輸入為什么類型,輸出應該也是什么類型。
1.8.3. 對象類型JS中的對象是由屬性和方法構成的,而TS中對象的類型就是在描述對象的結構(有什么類型的屬性和方法)。
1.8.3.1. 定義對象類型- 使用花括號( {} )來定義對象類型,屬性采用 屬性名: 類型 的形式;方法采用 方法名(): 返回值類型 的形式。
- 如果方法有參數,就在方法名后面的小括號中指定參數類型(比如:greet(name: string): void)。
- 在一行代碼中指定對象的多個屬性類型時,使用分號( ; )來分隔。
- 如果一行代碼只指定一個屬性類型(通過換行來分隔多個屬性類型),可以去掉分號( ; )。
- 方法的類型也可以使用箭頭函數形式,比如:{ sayHi: () => void }。
上面的代碼,也可以寫成下面這種形式:
對象類型中的屬性或方法可以是可選的,使用問號( ? )標記。
可選屬性表示該屬性可以存在,也可以不存在。
比如,我們在使用axios({...})時,如果發送GET請求,method屬性就可以省略。
對象的屬性也可以是只讀的,使用 readonly 關鍵字標記。
只讀屬性表示該屬性的值在創建后就不能被修改。
在 TypeScript 中,元組(Tuple)是一種特殊的數組類型,它允許 存儲具有固定數量和特定類型順序的元素。
聲明一個元組的語法是在類型注解中使用方括號 [] ,并使用逗號分隔元素的類型。
例如,下面是一個包含兩個元素的元組:
在上述示例中,我們聲明了一個名為 tuple 的變量,它被注解為一個元組類型 [string, number]。我們可以將一個包含兩個元素的數組賦值給 tuple,其中第一個元素是一個字符串,第二個元素是一個數字。
2.2. 元組的特點- 元組可以包含多個不同類型的元素,但每個元素的類型和順序是固定的。
- 元組的長度是固定的,在創建元組時必須指定元素的數量。
- 可以通過索引訪問元組中的元素,索引從 0 開始。
- 元組中的每個元素可以具有不同的類型注解。
- 當訪問元組中的元素時,會根據其類型注解提供相關的類型檢查和智能提示。
下面是一些操作元組的示例:
注意:當訪問元組中的元素以及進行元素的賦值時,要確保索引和類型注解的一致性,否則可能會導致類型錯誤。
2.3. 元組類型的解構賦值在 TypeScript 中,可以使用解構賦值語法來從元組中提取和賦值元素。
下面是一個簡單的示例,展示了如何使用解構賦值從元組中獲取各個元素:
在上述示例中,我們首先聲明了一個元組 tuple,其中包含一個字符串類型的元素和一個數值類型的元素。接著,我們使用解構賦值語法將元組中的元素分別賦值給變量 str 和 num。
通過解構賦值,我們可以直接使用對應位置的變量來獲取元組中的元素值,而不需要通過索引訪問。這樣可以以一種簡潔、語義明確的方式從元組中解構得到各個元素。
解構賦值還支持忽略某些元素,或者只提取部分元素。
例如,如果只想獲取元組中的第一個元素,可以使用以下方式:
在上述示例中,我們只聲明了一個變量 str,而忽略了后面的元素。通過解構賦值只獲取所需元素,可以簡化代碼并提高可讀性。
另外,解構賦值還支持使用默認值。
當從元組中解構一個不存在的元素時,可以提供一個默認值作為備選值。例如:
在上述示例中,我們聲明了一個帶有可選的數字元素的元組 tuple,但是沒有給出對應的數字值。在解構賦值時,如果元組中缺少對應的元素,就會使用默認值 undefined,這里我們將默認值設置為 26。
總而言之,使用解構賦值可以輕松地從元組中提取和賦值元素,使得代碼更加簡潔和可讀。它是一種方便的語法,特別適用于處理具有固定結構的數據。
注意:在解構賦值時,如果解構數組元素的個數超過元組中元素的個數,會出現錯誤。
在上述示例中,我們解構時新增了一個 sex 變量,但元組的長度為 2,在索引 "2" 處沒有元素。
在 TypeScript 中,可以使用問號 ? 來將元素定義為可選的,以表示元組中某些位置的元素是可選的。
在上述示例中,我們定義了一個元組 tuple,該元組有兩個元素,第一個是一個字符串類型的元素,而第二個是一個可選的數值類型的元素。當我們只提供第一個元素時,第二個元素會被默認設置為 undefined。然后,我們更新了元組的值,提供了第二個元素的值。此時,元組中的兩個元素都有具體的值。
注意,當一個元組中包含一個可選元素時,該元素可以存在或不存在,但是順序必須與元組類型定義一致。在解構賦值時,可以使用默認值來處理可選元素的缺失情況。
在上述示例中,我們使用解構賦值將元組中的元素分別賦值給變量 str 和 num。由于元組只提供了一個元素,沒有提供可選的第二個元素,所以 num 的值將使用默認值 26。
通過使用可選元素,可以更靈活地定義元組類型,允許元組中特定位置的元素是可選的。這樣,我們可以在處理數據時更好地適應不完整或可變的情況。
2.5. 元組類型的剩余元素在 TypeScript 中,可以使用剩余元素(Rest Elements)來表示元組中剩余的元素,即將剩余的元素放入一個數組中。
在上述示例中,我們定義了一個元組 tuple,包含一個字符串元素、一個數字元素,以及剩余元素使用剩余元素語法 ... 定義的布爾類型數組。在創建元組時,我們提供了多個布爾類型的元素,它們會被放入一個數組并作為剩余元素。這樣,元組中除了前兩個元素以外的其他元素都會被放入數組中,并以數組的形式表示。
在上述示例中,我們使用解構賦值從元組中提取出各個元素。通過使用 ...boolArr,我們將剩余的布爾類型元素提取到名為 boolArr 的數組中。
使用剩余元素可以處理元組中數量不確定的元素,可以更靈活地處理和操作這些元素。它提供了一種方便的方式來處理由不固定數量的元素組成的結構數據。
2.6. 只讀的元組類型在 TypeScript 中,可以使用 readonly 修飾符來創建只讀的元組類型,即元組中的元素不可被修改。
在上述示例中,我們使用 readonly 修飾符將 tuple 聲明為只讀的元組類型。這意味著在運行時,我們無法修改元組中的元素的值。
嘗試對 tuple 進行賦值或調用修改元素的方法(如 push)時,TypeScript 編譯器會報錯,因為元組被聲明為只讀,無法被修改。
只讀的元組類型在某些場景下非常有用,特別是當希望確保元組中的數據不會被意外修改時。它提供了一種強制保護元組數據不可變性的機制。
3. 字面量類型當我們在 TypeScript 中使用字面量類型,我們可以明確指定變量只能取特定的字面量值,而不是其他可能性。這樣可以在編譯時捕獲潛在的錯誤,并提供更好的類型推斷和類型檢查支持。
在 TypeScript 中,可以使用多種類型的字面量進行類型定義,包括字符串字面量類型、數字字面量類型、布爾字面量類型和符號字面量類型。
3.1. 字符串字面量類型使用字符串字面量表示的類型,只能取特定的字符串值。
使用數字字面量表示的類型,只能取特定的數字值。
使用布爾字面量表示的類型,只能取特定的布爾值。
使用符號字面量表示的類型,只能取特定的符號值。
字面量類型不僅可以用于變量的定義,還可以用于函數的參數、返回值、對象屬性等地方。通過使用字面量類型,我們可以在編寫代碼時明確指定特定的取值范圍,提高代碼的可讀性和可維護性。
需要注意的是,字面量類型具有一個特殊的用途,即與聯合類型結合使用,以實現更精確的類型約束。例如,聯合類型 string | number 表示可以是字符串或數字類型的值,而字面量類型 "success" | "error" 表示只能是字符串 "success" 或 "error",它們可以一起使用來實現更精確的類型定義。
在上述示例中,函數 move 的參數 direction 的類型被指定為 "up" | "right" | "down" | "left",這意味著參數 direction 只能接受這四個特定的值。
3.6. 函數返回值中的字面量類型在上述示例中,函數 getMove 的返回值被指定為 "up" | "right" | "down" | "left",這表示函數的返回值只能是這四個特定的值之一。
3.7. 對象屬性中的字面量類型在上述示例中,Options 接口中的 mode 屬性的類型被指定為 "light" | "dark",size 屬性的類型被指定為 "small" | "medium" | "large",這意味著對象 config 的 mode 屬性只能是其中一個值,size 屬性也只能是其中一個值。
3.8. let 和 const 分析3.8.1 let 聲明的字面量類型在上述示例中,我們使用 let 關鍵字聲明了變量 direction,并將其類型指定為 "Up" | "Right" | "Down" | "Left",因此 direction 只能取值為 "Up" 或 "Right" 或 "Down" 或 "Left" 這四個特定值中的其中一個。
3.8.2 const 聲明的字面量類型在上述示例中,我們使用 const 關鍵字聲明了常量 size,并將其類型指定為 "small" | "medium" | "large"。由于使用了 const,size 是一個只讀的常量,且初始值為 "medium"。因此,size 的值將永遠是 "medium",不能被重新賦值。
使用 let 和 const 關鍵字來聲明變量和常量時,可以配合字面量類型提供更具體和可靠的類型約束。
注意:const 聲明的常量在聲明時必須被初始化,并且一旦初始化后,其值將不能被修改。而 let 聲明的變量可以在后續代碼中被重新賦值。
4. 枚舉(Enum)枚舉(Enum)是一種用于定義一組命名常量的數據結構。
4.1. 基本枚舉在上述示例中,我們定義了一個名為 Direction 的枚舉,其中列出了 Up、Down、Left 和 Right 四個枚舉成員。默認情況下,枚舉成員的值從 0 開始自動遞增,因此 Direction.Up 的值為 0。我們可以使用枚舉成員來聲明變量,并進行比較、打印等操作。
4.2. 數字枚舉在默認情況下,數字枚舉的成員從 0 開始自動遞增。
4.2.1. 默認遞增的數字枚舉在上述示例中,我們定義了一個名為 Direction 的枚舉,其中列出了 Up、Down、Left 和 Right 四個枚舉成員。默認情況下,枚舉成員的值從 0 開始自動遞增,因此 Direction.Up 的值是 0,Direction.Down 的值是 1,Direction.Left 的值是 2,Direction.Right 的值是 3。
4.2.2. 手動賦值的數字枚舉在手動賦值的數字枚舉中,可以為每個枚舉成員手動指定一個特定的值。手動賦值的數字枚舉可以使用任意合法的數字作為成員的值。
在上述示例中,Direction.Up 被賦值為 2,Direction.Down 被賦值為 4,Direction.Left 被賦值為 6,Direction.Right 被賦值為 8。
4.2.3. 計算成員的數字枚舉在數字枚舉中,可以使用計算表達式作為成員的值。
在上述示例中,我們使用加法、減法、乘法和除法運算符來計算成員的值。在編譯時,這些計算表達式會被求值為結果值并成為實際的枚舉成員的值。
4.3. 常量枚舉常量枚舉(const enum)是一種特殊類型的枚舉,它在編譯時被刪除,并且只保留枚舉成員的值作為常量。常量枚舉提供了一種更輕量級的方式來使用枚舉,可以用于在編譯期間替換枚舉成員的值。
4.3.1. 常量枚舉的定義在定義常量枚舉時,需要使用 const 關鍵字和 enum 關鍵字的組合。常量枚舉不能有計算成員。
在 TypeScript 中,字符串枚舉是一種特殊類型的枚舉,其中每個成員都用字符串字面量進行初始化。
在上述示例中,我們定義了一個名為 Direction 的字符串枚舉。其中的成員 Up 使用字符串字面量 "UP" 進行初始化,成員 Down 使用字符串字面量 "DOWN" 進行初始化,成員 Left 使用字符串字面量 "LEFT" 進行初始化,成員 Right 使用字符串字面量 "RIGHT" 進行初始化。我們可以通過直接訪問枚舉成員來獲得其對應的字符串值。
字符串枚舉的特點:
- 明確的字符串值:每個字符串枚舉成員都具有明確的字符串值,可更好地描述其含義和用途。
- 代碼可讀性:由于成員的值直接使用字符串字面量,因此代碼更加清晰、易讀。
- 保留字符串字面量:使用字符串枚舉可以在編譯后保留字符串字面量,而不是轉換為數值或其他類型。
- 可用于反向映射:字符串枚舉可以支持從枚舉值到枚舉名的反向映射。
外部枚舉(ambient enum)是一種定義在外部代碼(如聲明文件)中的枚舉。外部枚舉通常用于描述已存在的枚舉類型的形狀,而不是為了創建一個具體的 JavaScript 對象。
外部枚舉的定義不會在編譯時生成任何實際的 JavaScript 代碼,它只用于類型檢查。
在上述示例中,我們使用 declare 關鍵字來定義了一個外部枚舉 HttpStatusCode。它描述了一些常見的 HTTP 狀態碼。其中的成員 OK 和 BadRequest 和 NotFound 指定了具體的數值,分別為 200,400 和 404,成員 Unauthorized 沒有顯式指定值,它會根據前一個成員的值自動遞增,因此值為 401。
在使用外部枚舉時,我們可以像使用普通枚舉一樣,訪問它的成員并獲得相應的值。在上述示例中,我們將 HttpStatusCode.OK 賦值給變量 code,然后將變量 code 的值打印出來,得到的結果是 200。
注意:當使用外部枚舉時,我們必須使用 declare 來聲明它,以告訴 TypeScript 編譯器這是一個外部定義的枚舉。此外,外部枚舉的定義通常是在一個聲明文件中(以 .d.ts 結尾),以便在與現有 JavaScript 庫或框架進行交互時提供類型信息。
總結起來,外部枚舉是 TypeScript 中一種在外部代碼中定義的枚舉,用于描述已存在的枚舉類型的形狀。外部枚舉的定義通常只用于類型檢查,并不會生成實際的 JavaScript 代碼。它在與現有 JavaScript 庫或框架進行交互時提供類型信息。
4.6. 異構枚舉異構枚舉(heterogeneous enum)是一種允許枚舉成員的值具有不同類型的枚舉。
通常情況下,枚舉中的成員的值應該是相同類型的。但是異構枚舉允許在同一個枚舉中使用不同類型的值,包括字符串、數字和其他類型。
在上述示例中,我們定義了一個名為 Status 的異構枚舉。其中的成員 Active 的值是一個數字,值為 1。成員 Pending 沒有顯式指定值,它的值會根據前一個成員的值自動遞增,因此值為 2。成員 Inactive 的值是一個字符串,值為 "inactive"。成員 OnHold 的值是一個字符串,值為 "on hold"。
在訪問異構枚舉的成員時,將得到其對應的值。在上述示例中,我們分別打印了每個異構枚舉成員的值,并相應地獲得了不同類型的結果。
異構枚舉的優勢在于允許在一組相關的枚舉中使用不同類型的值。這在某些特定情況下可能很有用,例如需要表示不同種類的狀態或類型時。
注意:在異構枚舉中,具有數字字面量值的成員會根據前一個成員的值自動遞增,而具有字符串字面量值的成員不會自動遞增。同時,在異構枚舉中,沒有初始化值的成員會根據前一個成員的值自動遞增。
4.7. 反向映射反向映射(reverse mapping)是指枚舉成員不僅可以通過名稱訪問值,而且可以通過值訪問名稱。 這意味著可以根據枚舉的值獲取到對應的枚舉成員名稱。
在上述示例中,我們定義了一個名為 Direction 的枚舉,其中的成員分別使用數字進行初始化。我們將 Direction.Right 的值賦給變量 rightValue,然后使用 Direction[rightValue] 獲取到對應的枚舉成員名稱,將結果賦給變量 rightName。
在打印出變量 rightValue 和 rightName 的值后,我們得到的結果是 4 和 Right。這就是反向映射的效果,根據枚舉的值可以獲取到對應的枚舉成員名稱。
注意:反向映射只在數字枚舉中有效,而不適用于字符串枚舉。 字符串枚舉的成員值雖然可以是字符串字面量,但在 JavaScript 中無法實現反向映射。
4.8. 運行時的枚舉運行時的枚舉(runtime enum)是指在 JavaScript 運行時可訪問和操作的枚舉。
TypeScript 編譯器在編譯過程中,會將枚舉類型轉換為實際的 JavaScript 對象。這些對象在運行時仍然保留了枚舉的結構和值,以便能夠通過它們來進行運行時的枚舉操作。
在上述示例中,我們定義了一個名為 Fruit 的枚舉,其中包含了三個成員 Apple、Orange 和 Banana。然后我們定義了一個函數 getFruitName,它接受一個 Fruit 類型的參數,根據傳入的枚舉值返回對應的水果名稱。
通過運行 getFruitName 函數并傳入不同的枚舉值,我們可以在控制臺上看到輸出的結果,它們是根據傳入的枚舉值返回的相應水果名稱。
注意:當使用運行時枚舉時,由于枚舉的成員值實際上是數字(默認從 0 開始遞增),因此進行比較時需要使用嚴格相等運算符 ===。
4.9. 聯合枚舉聯合枚舉(union enum)是指一個枚舉類型可以包含多個不同的枚舉成員的組合。每個成員可以具有不同的值和類型。
在上述示例中,我們定義了兩個枚舉 Shape 和 Color。Shape 枚舉表示不同的形狀,Color 枚舉表示不同的顏色。然后我們定義了一個類型別名 ShapeColor,它是 Shape 枚舉成員和 Color 枚舉成員的聯合。接著,我們定義了一個函數 drawShape,它接受一個 ShapeColor 類型的參數 shape。根據傳入的參數值進行不同的分支邏輯處理,并輸出相應的消息。通過調用 drawShape 函數并傳入不同的值,我們可以根據傳入的參數值來繪制不同的形狀或填充不同的顏色。
聯合枚舉使得我們能夠在一個類型中組合多個不同的枚舉成員,以表示更復雜的類型。這可以讓 TypeScript 的類型系統提供更精確的類型檢查和推斷,以確保代碼的正確性。
注意:聯合枚舉的使用是通過定義類型別名或接口來實現的。 通過將不同枚舉成員組合在一起,可以創建復合類型,提供更靈活的數據表示。
5. any類型在 TypeScript 中,any 類型表示一個動態類型,它可以接受任何類型的值。使用 any 類型時,TypeScript 編譯器將不會對值進行類型檢查,允許你在編譯期繞過類型系統的限制。
如果是一個普通類型,在賦值過程中改變類型是不被允許的。
如果是 any 類型,則允許被賦值為任意類型。
以下兩種情況,隱式具有 any 類型:
- 聲明變量不提供類型也不提供默認值。
- 函數參數不加類型。
注意:在開發過程中應盡量避免過度使用 any 類型,以充分利用 TypeScript 的類型系統來提供更好的類型安全性和代碼可維護性。
五、接口(interface)1. 什么是接口在 TypeScript 中,接口(Interface)是一種用來定義對象的結構和行為的類型。通過接口,我們可以定義對象應該有哪些屬性、屬性的類型以及方法。
接口提供了一種約束和規范,使得我們可以在代碼中定義和使用特定的數據結構。
2. 定義接口- 使用關鍵字 interface 來定義接口。
- 聲明接口后,直接使用接口名稱作為變量的類型。
- 方法的定義和函數的定義類似,包括參數和返回值類型。
- 接口一般首字母大寫。有的編程語言中建議接口的名稱加上前綴 “ I” ,“ 。”
上面的代碼中,我們定義了一個接口 Person,接著定義了一個變量 jerry,它的類型是 Person。這樣,我們就約束了 jerry 的形狀必須和接口 Person 一致。
注意:定義的變量比接口少了一些屬性不允許的。
下面是一段錯誤的代碼演示:我們定義了一個接口 Person,里面有name,age2個屬性,以及sayHi方法,接著定義了一個變量 jerry,它的類型是 Person,但是我們只給屬性name和age賦值,所以會報錯。
當然,定義的變量比接口多了一些屬性也是不允許的。
也就是說,在賦值的時候,變量的形狀必須和接口的形狀保持一致。
3. 接口(interface)和類型別名(type)的區別- 相同點:都可以用于定義對象的結構和類型。
- 不同點:
- 接口更適合用于描述真實存在的對象,而類型別名更適合用于定義復雜的類型。 接口可以被其他對象實現,而類型別名只是給類型起了一個別名。
在 TypeScript 中,接口是可以相互繼承的,也就是說:一個接口可以從另一個接口中繼承屬性和方法的定義(通過繼承實現復用)。 接口的繼承可以通過使用關鍵字 extends ****實現。
接口繼承的語法格式如下:
通過繼承,子接口可以獲得父接口中定義的屬性和方法,并可以在自身接口中添加新的屬性和方法。
下面是一個簡單的例子,展示了接口繼承的用法:
在上面的例子中,使用 extends 關鍵字實現了接口 Circle 繼承 Shape。繼承后,Circle 就有了 Shape 中的 color 屬性,以及自身的 radius 屬性以及 getArea() 方法。
5. 接口的可選屬性帶有可選屬性的接口與普通的接口定義差不多,只是在可選屬性名字定義的后面加一個 ? 符號。
上面的例子中,Person 接口中的 age 屬性是可選的,我們定義了 person1 和 person2 兩個對象,類型都是Person,其中,person1 對象中沒有 age 屬性,而 person2 對象中包含了 age 屬性。
可選屬性的好處有2個:
- 可以對可能存在的屬性進行預定義
- 可以捕獲引用了不存在的屬性時的錯誤
例如,我們故意將 person2 對象中的 age 屬性名寫錯,就會得到一個錯誤的提示。
有時候我們希望某些屬性在對象創建后不能被修改,可以將這些屬性聲明為只讀屬性。
通過在屬性名稱前面加上 readonly 關鍵字,就可以將屬性設置為只讀。
例如,下面的例子中,聲明了一個名稱為 Point2D 的接口,接口中的屬性 x 和 y 都是只讀的,然后創建了一個 point 對象,類型為 Point2D,此時,我們不能再給對象中的 x 和 y 重新賦值,會報錯,因為它們都是只讀屬性。
此外 TypeScript 還提供了 ReadonlyArray 類型,它與 Array 相似,只是把所有可變方法去掉了,因此可以確保數組創建后再也不能被修改。
接口用于定義對象的結構,當我們使用對象字面量賦值給接口類型時,TypeScript 會自動進行額外的屬性檢查。這意味著賦值的對象不能包含接口中未定義的額外屬性,否則會導致編譯錯誤。
在上述例子中,rect2 對象包含了額外的 color 屬性,但是接口 Rectangle 中并未定義該屬性,所以會導致編譯錯誤。
注意:如果我們確定對象會包含額外的屬性,可以使用類型斷言(Type Assertion)來繞過額外屬性檢查。
8. 接口的任意屬性有時候我們希望一個接口中除了包含必選和可選屬性之外,還允許有其他的任意屬性,這時我們可以使用 索引簽名 的形式來滿足上述要求。
上述代碼中,我們使用 [propName: string] 定義了任意屬性取 string 類型的值。
注意:一旦定義了任意屬性,那么必選屬性和可選屬性的類型都必須是它的類型的子集:
上述例子中,任意屬性的值允許是 string,但是可選屬性 age 的值卻是 number,number 不是 string 的子屬性,所以報錯了。
注意:一個接口中只能定義一個任意屬性。如果接口中有多個類型的屬性,則可以在任意屬性中使用聯合類型。
接口可以描述函數類型。
為了使用接口表示函數類型,我們需要給接口定義一個調用簽名。 它就像是一個只有參數列表和返回值類型的函數定義,參數列表里的每個參數都需要名字和類型。
在上述例子中,SearchFunc 是一個接口,它表示一個接收兩個參數 source 和 subString,參數類型都為 string,并且返回值為 number 類型的函數。
這樣定義后,我們可以像使用其它接口一樣使用這個函數類型的接口。
下面的例子展示了如何創建一個函數類型的變量,并將一個同類型的函數賦值給這個變量。
注意:對于函數類型的類型檢查來說,函數的參數名不需要與接口里定義的名字相匹配。
例如,我們使用下面的代碼重寫上面的例子:
函數的參數會逐個進行檢查,要求對應位置上的參數類型是兼容的。
如果你不想指定類型,TypeScript 的類型系統會推斷出參數類型,因為函數直接賦值給了 SearchFunc 類型變量。 函數的返回值類型是通過其返回值推斷出來的(此例是 false和true)。
如果讓這個函數返回數字或字符串,類型檢查器會警告我們函數的返回值類型與 SearchFunc 接口中的定義不匹配。
接口可以描述具有索引簽名的對象,這樣我們就可以通過索引來訪問對象的屬性。
上述的例子中,我們定義了 StringArray 接口,它具有索引簽名。這個索引簽名表示了當用 number 去索引StringArray 時會得到 string 類型的返回值。
TypeScript 支持兩種索引簽名:字符串和數字。可以同時使用兩種類型的索引,但是數字索引的返回值必須是字符串索引返回值類型的子類型。 這是因為當使用 number 來索引時,JavaScript 會將它轉換成 string 然后再去索引對象。 也就是說用 100(一個number)去索引等同于使用"100"(一個string)去索引,因此兩者需要保持一致。
11. 類類型實現接口接口可以被類實現,稱為類類型。
類可以通過 implements 關鍵字來實現接口,并必須實現接口中定義的所有屬性和方法。
在上述例子中,Document 類實現了 Printable 接口,并實現了接口中定義的 print 方法。
12. 繼承接口和類一樣,接口也可以相互繼承。 這讓我們能夠從一個接口里復制成員到另一個接口里,可以更靈活地將接口分割到可重用的模塊里。
一個接口可以繼承多個接口,創建出多個接口的合成接口。
當接口繼承了一個類類型時,它會繼承類的成員但不包括其實現。 就好像接口聲明了所有類中存在的成員,但并沒有提供具體實現一樣。 接口同樣會繼承到類的 private 和 protected 成員。 這意味著當你創建了一個接口繼承了一個擁有私有或受保護的成員的類時,這個接口類型只能被這個類或其子類所實現(implement)。
在以上示例中,我們定義了一個 Animal 類,它有一個 name 屬性和一個 eat 方法。然后,我們定義了一個接口 CanRun,它繼承自 Animal 類,并添加了一個 run 和 eat 方法。接著,我們創建了一個 Dog 類來實現 CanRun 接口,并在 Dog 類中實現了 run 和 eat 方法。
在最后的代碼中,我們使用 CanRun 接口來聲明一個 dog 對象,并將其實例化為 Dog 類的對象。這樣,我們可以通過調用 dog 對象的 eat 和 run 方法來驗證接口繼承類的實現。
接口繼承類的主要作用在于類型標注和約束。 通過接口繼承類,我們可以定義更具體的接口類型,使得類和接口之間的關系更加清晰。同時,在使用接口類型的變量或參數時,可以享受到類成員的類型檢查和智能提示的功能。這對于代碼的可讀性、可維護性和可擴展性都有很大的幫助。
六、類型別名作用:
在 TS 中,類型別名主要用于為已有的類型創建別名,以便在代碼中更方便地引用和重用這些類型。
用法:
- 使用 type 關鍵字可以為任何類型定義別名,包括基本類型、復雜類型、函數類型等。
- 創建類型別名后,直接使用該類型別名作為變量的類型注解即可。
解釋:
- 類型別名是為已有類型提供另一個名稱,而不是創建新的類型。
- 類型別名可以用于簡化復雜類型的表達,提高可讀性和可維護性。
- 類型別名可以用于定義聯合類型或交叉類型的別名。
注意:
- 盡量選擇有意義的別名,能夠準確描述類型的用途,提高代碼的可讀性。
- 避免過度使用類型別名,過多的別名可能導致代碼的可維護性變差。
- 注意避免循環引用的情況,即在類型別名中引用自身,這會導致編譯錯誤。
- 類型別名并不創建新的類型,所以它無法被繼承或實現。
在 TypeScript 中,類型推論(Type Inference)是指編譯器在沒有明確指定類型的情況下,根據變量的值推斷出該變量的類型。 通過類型推論,TypeScript 可以在代碼中自動推斷出變量的類型,而無需顯式地將其指定為特定類型。
2. 基本類型推論當聲明一個變量時,如果沒有顯式指定類型,并且在聲明的同時進行了賦值操作,TypeScript 將根據賦值的值推斷出變量的類型。
當變量的類型與其所處的上下文相關時,TypeScript 可以根據上下文進行類型推斷。
在上述示例中,函數 add 接收兩個參數,并返回它們的和。當我們調用 add(5, 10) 時,TypeScript 根據函數返回值的類型推斷出 result 變量的類型為 number。
4. 最佳通用類型推論當需要推斷出數組或對象類型時,TypeScript 會根據元素或屬性的類型推斷出一個“最佳通用類型”。
在上述示例中,數組 numbers 中的所有元素都是數字,因此 TypeScript 推斷出 numbers 的類型為 number[]。而數組 mixed 中的元素類型不同(數字、字符串和布爾值),所以 TypeScript 推斷出 mixed 的類型為 (number | string | boolean)[],表示該數組可以存儲數字、字符串或布爾值類型的元素。
5. 聲明變量但沒有賦值的情況如果聲明變量的時候沒有賦值,不管之后有沒有賦值,都會被推斷成 any 類型而完全不被類型檢查。
在上述示例中,變量 str 的類型推斷為 any 類型,因為它沒有明確的初始值。此時我們就可以把任意類型的值賦值給 str。
需要注意的是,雖然 TypeScript 可以根據賦值來推斷類型,但如果變量的初始值為 null 或 undefined,類型推論仍然會將其推斷為 any 類型。
為了避免使用 any 類型,我們可以顯式指定變量的類型或為變量提供一個初始值來觸發類型推論。
八、類型斷言1. 定義類型斷言(Type Assertion)是 TypeScript 中的一種表達式,它可以用來告訴編譯器一個值的確切類型。通過類型斷言,我們可以在一些情況下主動指定變量的類型,以滿足特定的需求。
2. 語法類型斷言有2種語法形式:
- 尖括號語法: 使用尖括號 <> 將值包裹,并在尖括號內指定目標類型。 <類型>值
在上面的示例中,我們將變量 value 的類型斷言為 string 類型,然后使用 .length 屬性獲取字符串的長度。
- as 語法: 使用 as 關鍵字,在值后面跟上目標類型。值 as 類型
在上面的示例中,我們使用 as 關鍵字將變量 value 的類型斷言為 string 類型,并用 length 屬性獲取字符串的長度。
以上兩種語法雖說沒有太大的區別,但是我們更推薦使用 as 語法。因為尖括號格式會與 react 中 JSX 產生語法沖突。
3. 任何類型可以斷言為 any 類型由于 any 類型可以接收任何值,因此任何類型都可以斷言為 any 類型。這樣的斷言并不提供更多的類型檢查,因此在使用類型斷言時需要謹慎。
上面的例子中,數字類型的變量 foo 上是沒有 length 屬性的,故 TypeScript 給出了相應的錯誤提示。
這種錯誤提示顯然是非常有用的。
但有的時候,我們非常確定這段代碼不會出錯,比如下面這個例子:
上面的示例中,我們需要將 window 上添加一個屬性 bar,但 TypeScript 編譯時會報錯,提示我們 window 上不存在 屬性 bar。
此時我們可以使用 as any 臨時將 window 斷言為 any 類型:
與上述情況相反,由于 any 類型可以接收任何值,它可以被斷言為任何類型。這樣的斷言會跳過類型檢查,因此潛在的類型錯誤可能發生。
當變量具有聯合類型時,我們可以通過類型斷言將其斷言為其中的一個類型,但是必須確保斷言的類型是變量實際上可以具備的類型。
類型斷言只是告訴編譯器將一個值視為特定類型,并不會改變該值的實際類型。在運行時,類型斷言不會影響變量的值或行為,它只是在編譯時起作用。
6.2. 類型斷言不能用于基本類型之間的轉換TypeScript 的類型斷言不能用于將基本類型(如 number、string、boolean)相互轉換。因為基本類型具有明確的類型判斷和行為,不能將一個基本類型斷言為另一個基本類型。
類型斷言可以繞過編譯器的類型檢查,但并不意味著我們可以隨意斷言任何類型。如果發生類型斷言與變量的實際類型不匹配的情況,可能會導致運行時錯誤。
因為 null 和 undefined 可以被賦值給任何類型,將它們斷言為其他類型是沒有意義的。
如果將一個變量斷言為聯合類型中某個類型,那么它必須是該聯合類型中的實際類型之一。
雙重斷言(Double Assertion),也被稱為雙重類型斷言或連續類型斷言,是一種在 TypeScript 中連續使用類型斷言的技術。它是將一個值斷言為多個類型的一種嘗試,盡管這種用法并不被 TypeScript 官方鼓勵使用,因為它可能產生不可預測的結果。
雙重斷言的形式是使用連續的類型斷言操作符 as 或尖括號 <> 來表示:
在上述示例中,我們連續使用了兩次類型斷言,將值 value 先斷言為 any 類型,然后再將其斷言為 string 類型,并使用 length 屬性獲取字符串的長度。但是需要注意的是,盡管代碼通過了編譯,但是這種雙重斷言的方法并不安全,因為它可以導致類型錯誤和運行時錯誤。
使用雙重斷言可能會隱藏類型錯誤,因為類型斷言是編譯時的操作,而不是運行時。在運行時,雙重斷言可能會導致意外的類型轉換錯誤,并且編譯器無法為此提供任何保護。
所以,在實際開發中,應盡量避免使用雙重斷言。如果需要使用多個類型,而無法使用更安全的方法來表示,可以考慮重構代碼,使用更合適的類型來處理多種情況,或者使用類型守衛和類型判斷等 TypeScript 提供的更安全的技術來處理復雜的類型轉換或條件判斷。
8. 類型斷言VS類型轉換在 TypeScript 中,類型斷言(Type Assertion) 是一種在編譯時告訴編譯器一個值的確切類型的方式,它只是一種類型的聲明,不會對變量進行真正的類型轉換。
與類型斷言相對的是類型轉換(Type Casting) ,它是將一個值從一種類型轉換為另一種類型的實際操作,而不僅僅是告訴編譯器某個值的類型。類型轉換通常需要在運行時進行,并涉及對值的實際修改。
在上述示例中,(value as string) 是一種類型斷言,告訴編譯器將變量 value 視為字符串類型。而 parseInt 是一種類型轉換,將字符串類型的 numberValue 轉換為整數類型。
需要注意的是,類型斷言只會在編譯時起作用,不會對變量進行實際的類型轉換。而類型轉換涉及到對變量值的修改,通常發生在運行時。
盡管類型斷言和類型轉換在某種程度上可以實現相似的效果,但它們的機制和目的不同。類型斷言是為了輔助編譯器進行類型推斷和類型檢查的工具,而類型轉換是為了實際修改變量的類型以滿足特定需求。因此,在使用類型轉換時,需要注意潛在的類型錯誤和運行時錯誤,并謹慎處理類型轉換的結果。
9. 類型斷言VS類型聲明在 TypeScript 中,類型斷言(Type Assertion) 是一種在編譯時告訴編譯器一個值的確切類型的方式,它是開發者主動指定一個變量的類型,并告訴編譯器遵循這個類型進行類型檢查。通過類型斷言,我們可以在某些情況下繞過編譯器的類型檢查,但這需要開發者對類型的準確性負責,并且存在潛在的類型錯誤的風險。
在上述示例中,(value as string) 是一種類型斷言,將變量 value 的類型斷言為字符串類型,從而可以安全地訪問字符串的 length 屬性。
類型聲明(Type Declaration) 是一種為變量、參數、返回值等明確指定類型的語法,它是用來定義變量的類型,并告訴編譯器如何對變量進行類型推斷和類型檢查。類型聲明通常出現在變量聲明、函數聲明、函數參數、函數返回值等地方,例如:
在上述示例中,value: string 是對變量 value 進行類型聲明,指定其類型為字符串。而 name: string 是對函數參數 name 進行類型聲明,指定其類型為字符串。這樣可以確保編譯器在類型檢查時能夠發現潛在的類型錯誤。
類型聲明是 TypeScript 中一種重要的類型系統的特性,它提供了對變量類型的明確說明,使開發者能夠編寫更加安全和可維護的代碼。與類型斷言相比,類型聲明更加強制,能夠更好地幫助開發者在編譯時發現類型錯誤,并提供更好的類型推斷和類型檢查支持。
10. 類型斷言和泛型在 TypeScript 中,類型斷言(Type Assertion) 是一種在編譯時告訴編譯器一個值的確切類型的方式,它是開發者主動指定一個變量的類型,并告訴編譯器遵循這個類型進行類型檢查。通過類型斷言,我們可以在某些情況下繞過編譯器的類型檢查,但這需要開發者對類型的準確性負責,并且存在潛在的類型錯誤的風險。
在上述示例中,(value as string) 是一種類型斷言,將變量 value 的類型斷言為字符串類型,以便可以安全地訪問字符串的 length 屬性。
泛型是一種在定義函數、類或接口時使用類型參數來表示靈活的類型的方式。通過泛型,我們可以在定義時不指定具體類型,而是在使用時根據上下文傳入具體的類型。它可以增加代碼的重用性和靈活性。例如:
在上述示例中,toArray 是一個泛型函數,使用類型參數 T 來表示數組中的元素類型。通過傳入具體的類型 "Hello",我們可以創建一個字符串類型的數組。
類型斷言和泛型實際上可以一起使用。當我們在處理泛型類型時,有時可能需要對類型進行斷言以滿足特定的需求。例如:
在上述示例中,通過連續使用類型斷言,我們將泛型類型 T 先斷言為 unknown 類型,然后再斷言為字符串類型,將參數 value 轉換為字符串類型并返回。
需要注意的是,在使用類型斷言和泛型時,我們要確保類型的安全性和正確性,并避免潛在的類型錯誤。類型斷言可以幫助我們處理一些特殊情況,但要謹慎使用,并確保斷言的類型與變量的實際類型相符。泛型則是一種更加靈活和通用的方式來處理不特定類型的代碼邏輯。
九、類(class)1. 類的定義在 TypeScript 中,可以使用 class 關鍵字來定義類。類的定義通常包括成員變量、構造函數、方法等。
2. 類的基本使用類的基本使用主要有以下幾個步驟:
- 定義類及成員變量: 使用 class 關鍵字定義一個類,并在類中聲明成員變量。
- 構造函數: 使用 constructor 方法定義構造函數,用于在創建類的實例時初始化對象的屬性。
- 方法: 在類中定義方法,可通過類的實例調用。
- 創建類的實例: 使用 new 關鍵字創建類的實例,并傳遞構造函數所需的參數。
- 訪問成員變量和調用方法: 通過實例對象訪問成員變量和調用方法。
在上述示例中:我們使用 class 關鍵字定義一個名為 Person 的類,并在 Person 類中聲明了兩個成員變量:name 和 age。接著,我們使用 constructor 方法定義一個構造函數,用于在創建類的實例時初始化對象的屬性,構造函數參數 name 和 age 分別用于接收傳入的 name 和 age 值,并將其賦給對應的成員變量。然后定義了一個名為 sayHello 的方法,用于打印一個問候語,并使用成員變量 name 和 age。接著,我們使用 new 關鍵字創建一個 Person 實例 p,然后打印出 name 和 age 的值以及調用 sayHello 方法。
3. 類的構造函數在 TypeScript 類中,構造函數是一種特殊的方法,用于在創建類的實例時進行初始化操作。構造函數使用 constructor 關鍵字來定義,可以接收參數,并在創建對象時調用。
3.1. 構造函數的基本語法在上面的代碼中,ClassName 是類的名稱,parameter1、parameter2 等表示構造函數的參數名,Type1、Type2 等表示參數的類型。
3.2. 使用構造函數初始化成員變量構造函數可以用來初始化類中的成員變量,通過接收構造函數的參數,并將其賦給對應的成員變量。成員變量的聲明通常放在類的頂部,而初始化則在構造函數中進行。
在上述示例中,構造函數接收 name 和 age 作為參數,并將參數的值分別賦給類中的 name 和 age 成員變量。
3.3. 創建類的實例并調用構造函數使用 new 關鍵字創建類的實例時,構造函數會被自動調用,讓我們可以在創建實例的同時進行初始化操作。
在上述代碼中,我們創建了一個 Person 類的實例 person,并傳遞了 'Echo' 和 26 作為構造函數的參數。構造函數會將這些參數的值分別賦給 person 實例的 name 和age 成員變量。
3.4. 構造函數的可選參數和默認值構造函數的參數可以設置為可選的,并且可以為參數提供默認值。
可選參數使用問號( ? )修飾符進行標記,而默認值則使用等號(=)進行賦值。
在上述示例中,name 參數具有一個默認值 'Echo',而 age 參數則是可選的。如果在創建實例時不傳 name 和 age 參數,那么 name 會輸出默認值 'Echo',而 age 會被設置為 undefined,如果在創建實例時只傳遞了 name 參數,而沒有傳遞 age 參數,那么 age 也會被設置為 undefined。
3.5 .調用其他構造函數(構造函數重載)在一個類中,可以定義多個構造函數,并通過不同的參數配置來進行重載。重載的構造函數之間可以相互調用,使用 this 關鍵字來引用當前類的實例。
構造函數重載需要定義多個具有不同參數類型和數量的構造函數簽名。構造函數簽名是指構造函數名稱和參數列表,通過這些不同的簽名來區分不同的構造函數。
在上面的示例中,我們定義了三個構造函數簽名,每個簽名有不同的參數類型和數量,以提供不同的構造函數選項。
在上述示例中,我們定義了兩個構造函數簽名,第一個構造函數接收一個 name 參數,第二個構造函數接收一個 name 和一個 age 參數。在構造函數的實現中,根據傳遞的參數情況,決定是否給 age 成員變量賦值。接著,我們創建了兩個實例 person1 和 person2,第一次實例化傳遞了一個 name 參數,調用了第一個構造函數。第二次實例化傳遞了一個 name 參數和一個 age 參數,調用了第二個構造函數。
注意:
- 成員初始化(比如 name: string)后,才可以通過 this.name 來訪問實例成員。
- 需要為構造函數指定類型注解,否則會被隱式推斷為 any 類型,構造函數不需要返回值類型。
在 TypeScript 類中,實例方法是定義在類中的成員方法,用于操作和訪問類的實例屬性,并執行特定的操作。實例方法可以通過類的實例來調用,用于對特定實例進行特定操作。
4.1. 定義實例方法實例方法是通過在類中定義普通函數來創建的。語法格式如下:
在上面的示例中,methodName 是實例方法的名稱,parameter1 和 parameter2 是方法的參數,Type1 和 Type2 是參數的類型,ReturnType 是方法的返回類型。
4.2. 訪問實例屬性實例方法可以通過使用 this 關鍵字直接訪問類的實例屬性。
在上述示例中,sayHello 是一個實例方法,它訪問了 Person 類的 name 和 age 屬性,并在控制臺打印出相應的消息。
4.3. 調用實例方法實例方法必須通過類的實例來調用。
在上述示例中,我們首先創建了一個 Person 類的實例 person,然后使用 person 實例來調用 sayHello 方法。
5. 類的繼承類的繼承有2種方式:
- extends(繼承父類)
- implements(實現接口)
說明:JS 中只有 extends,而 implements 是 TS 提供的。
5.1. extends(繼承父類)當一個類繼承另一個類時,它會繼承父類的屬性和方法,并可以通過重載或添加新的屬性和方法來擴展父類。繼承使用 extends 關鍵字來建立類之間的關系。
5.1.1. 定義父類和子類父類是被繼承的類,子類是繼承父類的類。
在上面的示例中,ParentClass 是父類,ChildClass 是子類,ChildClass 繼承了 ParentClass 的屬性和方法。
5.1.2. 繼承父類的屬性和方法使用 extends 關鍵字來建立子類對父類的繼承關系。子類會繼承父類的公共成員(屬性和方法)。子類可以直接訪問和使用繼承來的屬性和方法。
在上述示例中,Animal 是父類,其中包含了 name 屬性和 move 方法。Dog 是子類,使用 extends Animal 建立了繼承關系。Dog 繼承了 Animal 的屬性和方法,并且定義了自己的 bark 方法。
5.1.3. 調用繼承的屬性和方法子類可以直接調用繼承來的父類屬性和方法,也可以訪問自己定義的屬性和方法。
在上述示例中,我們首先創建了一個 Dog 類的實例 dog。我們可以通過 dog 實例調用繼承自父類的 move 方法,也可以調用子類自己定義的 bark 方法。
5.2. implements(實現接口)接口的實現是以類為基礎的,類可以通過 implements 關鍵字實現一個或多個接口。通過實現接口,類必須提供接口中定義的所有屬性和方法的具體實現。
5.2.1. 定義接口接口是一種抽象的類型,定義了一組屬性和方法的規范。接口在定義時不包含具體的實現,而是描述了類應具備的特定行為和功能。
在上面的示例中,InterfaceName 是一個接口,用于定義屬性和方法的規范。
5.2.2. 使用 implements 實現接口使用 implements 關鍵字來實現接口,使得類能夠滿足接口定義的規范。通過實現接口,類必須提供接口中定義的所有屬性和方法的具體實現。
在上述示例中,ClassName 是一個類,通過 implements InterfaceName 實現了接口 InterfaceName,從而滿足了接口定義的規范。
5.2.3. 實現接口的屬性和方法實現接口的類必須包含接口中定義的所有屬性和方法,并提供它們的具體實現。
在上面的示例中,Shape 是一個接口,定義了屬性 color 和方法 getArea()。Circle 類通過 implements Shape 實現了接口 Shape,并提供了接口中定義的屬性和方法的具體實現。
6. 類的修飾符在 TypeScript 中,類的修飾符用于控制類的成員(屬性和方法)的可見性和訪問權限。
類的修飾符包括:
- public(公有的),可以在任何地方被訪問到,默認所有的屬性和方法都是 public 的。
- privete(私有的),不能在聲明它的類的外部訪問。
- protected(受保護的),和 private 類似,區別是它在子類中也是允許被訪問的。
public 關鍵字是默認的訪問修飾符,如果不指定修飾符,默認為 public。公共成員在類的內部和外部都是可見的,并且可以隨時訪問。
在上述示例中,name、age 和 sayHello() 都是公共成員,可以在類的內部和外部進行訪問。
6.2. privateprivate 關鍵字修飾符限制成員的訪問范圍僅在類的內部。私有成員在類的外部不可見,只能在類的內部進行訪問。
在上述示例中,成員 name 是私有成員,只能在類的內部進行訪問,外部訪問會報錯。
注意:1. 使用 private 修飾的屬性或方法,在子類中也是不允許訪問的。
注意:2. 當構造函數修飾為 private 時,該類不允許被繼承或者實例化。
protected 關鍵字修飾符限制成員的訪問范圍在類的內部及其派生類中。受保護成員在類的外部不可見,但可以在類的內部和派生類中進行訪問。
注意:當構造函數修飾為 protected 時,該類只允許被繼承。
readonly 是一個只讀屬性關鍵字,只允許出現在屬性聲明或索引簽名或構造函數中。
注意:如果 readonly 和其他訪問修飾符同時存在的話,需要寫在其后面。
readonly 只讀屬性特點:
- 只讀屬性必須在聲明時或索引簽名或構造函數內進行初始化賦值。
- 只讀屬性不能被重新賦值或修改,否則會報錯。
- 只能修飾屬性,不能修飾方法。
只讀屬性和常量的區別:
- 只讀屬性是 TypeScript 提供的一種語法,用于將類的屬性標記為只讀,并且只有在類的內部可以修改其值。
- 常量通常是通過 const 關鍵字聲明的,在任何地方都無法修改其值,包括類的內部。
參數屬性是一種簡化代碼的語法糖,用于在構造函數中同時聲明和初始化類的成員屬性。使用參數屬性可以在一個地方完成屬性的聲明和賦值,減少了重復的代碼。
在上述示例中,定義了一個名為 Person 的類,類里面定義了一個 constructor 構造方法,其中參數 name 是公共屬性,可以在類的內部和外部訪問;參數 age 是私有屬性,只能在類 Person 中訪問;參數 sex 是受保護屬性,只能在類 Person 及其子類中訪問;參數 height 是只讀屬性,類的外部無法修改其值。
7. 抽象類使用關鍵字 abstract 用于定義抽象類和其中的抽象方法。
抽象類是一種不能直接實例化的類,它主要用作其他類的基類。抽象類可以包含抽象方法和具體方法的定義,供子類繼承和實現。
7.1. 語法在上述示例中,AbstractClass 是一個抽象類,它包含了一個抽象方法 method() 和一個具體方法 concreteMethod()。
7.2. 抽象方法抽象方法是在抽象類中聲明但沒有具體實現的方法。它只包含方法的簽名,沒有方法體,子類必須實現抽象方法。
在上述示例中,抽象類 Animal 中的 sayHi() 是一個抽象方法,子類 Cat 繼承了 父類 Animal 并實現了抽象方法。
7.3. 抽象類不能被實例化,只能被繼承抽象類不能被實例化,只能被繼承。
- 抽象類不能被實例化,只能被繼承。
- 抽象類可以包含抽象方法和具體方法的定義。
- 子類必須實現抽象類中的所有抽象方法,否則子類也必須聲明為抽象類。
- 如果一個類繼承了一個抽象類,那么它必須實現抽象類中的抽象方法,除非它自身也聲明為抽象類。
- 抽象類可以作為其他類的基類,用于提供共享的屬性和方法定義。
類型兼容性是指在 TS 中,如何判斷一個類型是否能夠賦值給另一個類型。
1. 基本類型的兼容性1.1. 相同的基本類型可以互相賦值當你聲明一個變量并為其賦予一個特定類型的值時,TypeScript 會根據類型注解進行類型檢查和推斷。如果變量的類型與給定的值的類型完全匹配,那么它們可以互相賦值。
在上述示例中,變量 a 被聲明為 number 類型,并且被賦值為 10。 然后將變量 a 賦值給變量 b,因為 a 和 b 的類型相同,都是 number,所以賦值是允許的。
1.2. 數字字面量類型可以賦值給數值類型當你聲明一個變量并為其指定為數字字面量類型時,TypeScript 會將該變量視為一個特定的數字值,而不僅僅是一般的數值類型。
在這個示例中,變量 a 被聲明為數字字面量類型 10,它只能具有值 10,而不能是其它的值。然后將變量 a 賦值給變量 b,因為 b 的類型是 number,而 a 是數字字面量類型 5,數字字面量類型是數字類型的子類型,所以賦值是允許的。
需要注意的是,只有字面量類型才可以賦值給相應的數值類型,普通數值類型不能賦值給字面量類型,除非兩者完全匹配。
1.3. 枚舉類型可以賦值給數字類型枚舉類型在 TypeScript 中被編譯成了一個具有反向映射的對象。默認情況下,枚舉類型的成員值是從 0 開始遞增的數字。由于枚舉成員值是數字類型,所以它們可以被賦值給數字類型。
在上述示例中,將 Direction.Right 賦值給了枚舉類型的變量 direction,然后又將 direction 賦值給了數字類型的變量 num,此時 num 的值為 1,與 Direction.Right 對應的枚舉成員值相同。
需要注意的是,枚舉類型不僅可以賦值給數字類型,也可以賦值給字面量類型或其他兼容的類型。這主要是由于 TypeScript 在類型系統中對枚舉類型進行了特殊處理,使得枚舉成員值可以被當作相應的字面量值使用。
2. 對象類型的兼容性對象類型包括接口(interface)、類(class)、字面量對象等。
記住這句話:成員多的可以賦值給成員少的。
2.1. 成員個數的兼容性對象類型 T 能夠賦值給對象類型 U,需要滿足的條件是 T 中的成員個數要大于等于 U 中的成員個數。也就是說,T 可以擁有 U 中的所有成員,但 T 可能還有額外的成員。
在上述示例中,類 Point2D 具有 x 和 y 成員,類 Point3D 比類 Point2D 多了一個 z 成員,根據兼容性規則,Point3D 可以賦值給 Point2D,因為類 Point3D 擁有類 Point2D 中的所有成員。
2.2. 成員類型的兼容性對象類型 T 能夠賦值給對象類型 U,需要滿足的條件是 T 中的每個成員的類型都能夠賦值給 U 中對應成員的類型。這個規則適用于成員變量和成員函數。
對象類型 T 能夠賦值給對象類型 U,如果 U 中定義了可選屬性,且 T 中沒有對應的屬性,則仍然可以進行賦值。
函數之間的兼容性會比較復雜,需要考慮以下幾個方面:
- 參數個數
- 參數類型
- 返回值類型
源函數的參數個數要小于等于目標函數的參數個數。也就是說,源函數可以接受更少的參數或與目標函數相同數量的參數。多余的參數是允許的,因為在函數調用時可以忽略它們。
記住這句話:參數少的可以賦值給參數多的。
在上述示例中,我們定義了兩個類型 Adder 和 Calculator 分別表示加法函數和計算函數。根據函數兼容性規則,add 可以賦值給 calculate,因為 Adder 的參數個數(2個)少于 Calculator 的參數個數(3個)。但是相反的賦值會導致兼容性錯誤,因為 Calculator 的參數個數(3個)要多于 Adder 的參數個數(2個)。
3.2. 參數類型在上述示例中,函數 x 的參數只有一個 a,類型為 number,函數 y 的參數有兩個 a 和 b,類型分別為 number 和 string,x 可以賦值給 y,是因為 x 的每個參數都能在 y 里找到對應類型的參數。 注意的是參數的名字相同與否無所謂,只看它們的類型。 而 y 不能賦值給 x,因為 y 有個必需的第二個參數,但是 x 并沒有,所以不允許賦值。
3.3. 返回值類型如果返回值類型是普通類型,此時函數的返回值類型要相同。
如果返回值類型是對象類型,此時成員多的可以賦值給成員少的。
類與對象字面量和接口差不多,但有一點不同:類有靜態部分和實例部分的類型。 比較兩個類類型的對象時,只有實例的成員會被比較。 靜態成員和構造函數不在比較的范圍內。
私有的和受保護的成員必須來自于相同的類或者父類的派生類。
當泛型類型沒有明確指定類型參數時,它被認為是一種特殊的兼容性形式,稱為類型參數的默認,即泛型函數或泛型類在沒有傳遞類型參數的情況下,它們的類型參數會被推導為any。此時,泛型類型可以兼容任意類型,也能賦值給其他泛型類型。
當泛型類型明確指定了類型參數時,要求類型參數具有兼容的類型。這意味著泛型類型在傳遞不同類型參數時,需要確保它們之間滿足兼容性規則。
交叉類型類似于接口繼承,是將多個類型合并為一個類型。 也就是說我們可以把現有的多種類型疊加到一起成為一種類型,它包含了所需的所有類型的特性。
使用符號( & )來定義交叉類型。
1. 組合對象類型在上述示例中,我們定義了 User 和 Admin 兩個類型,然后使用交叉類型 & 將 User & Admin 連接起來創建了一個新的類型 UserAdmin,該類型包含了 User 和 Admin 類型的所有成員,接著我們定義了一個變量 userAdmin,該變量同時具有 User 和 Admin 類型的屬性和方法。
2. 合并函數類型在上述示例中,我們定義了兩個函數類型 AddFunc 和 MultiplyFunc,AddFunc 里面定義了 fn 函數,MultiplyFunc 里面定義了 fn1 函數,并使用交叉類型 & 將 AddFunc & MultiplyFunc 連接起來創建了一個新的類型 MathOperations。此時變量 mathOps 同時擁有 fn 和 fn1 兩個方法。
3. 交叉類型VS接口繼承- 相同點:都可以實現對象類型的組合。
- 不同點:兩種方式實現類型組合時,對于同名屬性之間,處理類型沖突的方式不同。
下面是接口繼承的示例,接口B繼承接口A,兩個接口都定義了 fn 方法,返回值都是 string 類型,但是參數的類型不同,一個 string,一個 number,由于 fn 參數 value 的類型不兼容,所以接口 B 不能繼承接口 A。
下面是交叉類型的示例:我們定義了 A 和 B 兩個接口,然后使用交叉類型 & 將 A & B 連接起來創建了一個新的類型 ,接著我們定義了一個變量 c,類型為 C,變量 c 調用 fn 方法,此時參數的類型我們可以傳數字類型或者字符串類型。
如果合并的多個接口類型存在同名屬性會是什么效果呢?
在上面示例中,定義了兩個類型 User 和 Admin,其中類型 User 中有 id 和 name 屬性,類型 Admin 中有 name 和 age 屬性,兩個類型都有同名的 name 屬性,但類型不同,一個是 string,一個是 number,合并后,name 屬性的類型就是 string 和 number 兩個原子類型的交叉類型,即 never。
此時,我們如果賦予 user 任意類型的 name 屬性值都會提示類型錯誤。而如果我們不設置 name 屬性,又會提示一個缺少必選的 name 屬性的錯誤。在這種情況下,就意味著上述代碼中交叉出來的 UserAdmin 類型是一個無用類型。
如果同名屬性的類型兼容,比如一個是 number,另一個是 number 的子類型、數字字面量類型,合并后 name 屬性的類型就是兩者中的子類型。
在上面示例中,name 屬性的類型就是數字字面量類型 2,因此,我們不能把任何非 2 之外的值賦予 name 屬性。
如果交叉類型中的某個成員是對象類型,那么交叉后的類型將擁有這些對象類型的所有屬性
泛型(Generics)是 TypeScript 中一種允許我們在定義函數、類或接口時使用參數化類型的機制。泛型可以看作是類型參數,類似于函數中的參數,但是用于表示類型而不是值。它允許我們在定義函數、類或接口時使用占位符表示類型,并在實際使用時指定具體的類型。
2. 一個簡單的例子現在我們有個需求:實現一個函數,傳入的函數參數是什么類型的,返回值的類型也要跟函數參數的類型相同,并且函數只能接收一個參數,你會怎么做?
上面的示例中,我們創建了一個 identity 函數,參數值和返回值類型都為 number,調用 identity 函數,傳入一個數字,會返回數字本身。但是,該函數只能接收數值類型,如果我調用函數的時候傳入字符串或者布爾值類型的值,此時就會報錯。
為了讓函數能夠接收任意類型,可以將參數類型改為any,但是,這樣就失去了 TS 的類型保護,類型不安全。
為了解決上面的這些問題,我們使用泛型對上面的代碼進行重構。 泛型在保證類型安全(不丟失類型信息)的同時,可以讓函數等于多鐘不同的類型一起工作,靈活可復用。
上面示例中,我們在函數名 identity 后添加了 ,其中 T 代表 Type,在定義泛型時通常用作第一個類型變量名稱。但實際上 T 可以用任何有效名稱代替。在調用函數 identity 時,在<>中指定類型 string,此時參數和返回值類型也都為 string。
3. 泛型語法- 在函數名稱的后面添加尖括號( <> ),尖括號中添加類型變量,比如下圖中的 T。
- 其中 T 代表 Type,可以是任意合法的變量名稱。
- 類型變量 T,是一種特殊類型的變量,它用于處理類型而不是值。
- 該類型變量相當于一個類型容器,能夠捕獲用戶提供的類型(具體是什么類型,由用戶調用該函數時指定)。
- 因為 T 是類型,因此可以將其作為函數參數和返回值的類型,表示參數和返回值具有相同的類型。
在下面的示例中,調用泛型函數 identity,當傳入類型 number 后,這個類型就會被函數聲明時指定的類型變量 T 捕獲到,此時,T 的類型就是 number,所以,函數 identity 的參數和返回值的類型也都是 number。
- 在調用泛型函數時,可以省略<類型>來簡化泛型函數的調用。
- 此時,TS 內部會采用一種叫做類型參數推斷的機制,來根據傳入的實參自動推斷出類型變量 T 的類型。
- 當編譯器無法推斷類型或者推斷的類型不準確時,就需要顯示地傳入類型參數。
比如,傳入實參10,TS 會自動推斷出變量 num 的類型 number,并作為 T 的類型。
定義泛型的時候,可以一次定義多個類型參數:
上述示例中,我們定義了一個 swap 函數,用來交換輸入的元組。
6. 泛型類泛型類(Generic Class)是指在定義類時使用泛型類型參數的類。它允許我們在類的屬性、方法、構造函數以及實例化時使用泛型。
- 在 class 名稱后面添加 <類型變量> ,這個類就變成了泛型類。
- 在創建 class 實例時,在類名后面通過 <類型> 來指定明確的類型。
下面是一個簡單的泛型類的示例:
- 在接口名稱的后面添加 <類型變量> ,那么,這個接口就變成了泛型接口。
- 接口的類型變量,對接口中所有其它成員可見,也就是接口中所有成員都可以使用類型變量。
- 使用泛型接口時,需要顯示指定具體的類型。
下面是一個簡單的泛型接口的示例:
在 TypeScript 2.3 以后,我們可以為泛型中的類型參數指定默認類型。當使用泛型時沒有在代碼中直接指定類型參數,從實際值參數中也無法推測出時,這個默認類型就會起作用。
默認情況下,泛型函數的類型參數 T 理論上是可以是任何類型的,不同于 any,你不管使用它的什么屬性或者方法都會報錯(除非這個屬性和方法是所有集合共有的)。
比如下面的示例中,我想打印出參數的 length 屬性,如果不進行泛型約束 TS 是會報錯的:類型“T”上不存在屬性“length”。
報錯的原因很明顯,如果要解決這個問題,我們就可以通過給泛型(類型變量)添加約束。
下面我們通過 extends 關鍵字進行類型約束:
在上述示例中,我們定義了一個 ILength 接口,具有 length 屬性。在泛型函數 getLength 中,使用 T extends ILength 進行約束,該約束表示:傳入的類型必須具有 length 屬性。
十三、TS中的關鍵字TS 內置了一些常用的工具類型,來簡化 TS 中一些常見的操作,它們都是基于泛型實現的,并且是內置的,所以可以直接使用。
在學習工具類型之前,我們先學習一些關鍵字和基礎知識,以便我們可以更好的去學習后面的內置工具類型。
1. keyof在 TS 中,keyof 操作符主要用途是用于獲取類型中所有鍵的關鍵字。它用于泛型中,通常與索引類型(index type)結合使用。其返回類型是聯合類型。
下面示例中,我們定義了一個接口 Person,包含 name、age 和 gender 三個鍵,然后使用 keyof 來獲取 Person 接口的所有鍵,這樣,Keys 類型就是一個由 "name" | "age" | "gender" 構成的聯合字面量類型。
下面示例中,我們創建一個函數來獲取對象中屬性的值:
在 TS 中, 是一種泛型約束方式,用于限制一個泛型類型參數 key 的范圍。extends 關鍵字表示限制 key 的取值只能是 Type 類型中已有的屬性名。可以理解為:Key 只能是 Type 所有鍵中的任意一個,或者說只能訪問對象中存在的屬性。
在上面的例子中,getProp 函數接收兩個參數:一個泛型類型參數 Type,代表輸入對象的類型;一個泛型類型參數 Key,代表屬性名的類型。keyof Type 實際上獲取的是 person 對象所有鍵的聯合字面量類型,也就是:'name' | 'age' | 'gender',當我們調用調用 getProp 函數傳入一個不存在的屬性名,例如: 'school' 會引發編譯錯誤。
在 TS 中,typeof 操作符的主要用途是在類型上下文中獲取變量或者屬性的類型。
2.1. typeof獲取變量的聲明類型在 TS 中,typeof 可以用來返回一個變量的聲明類型,如果不存在,則獲取該類型的推論類型。
需要注意的是:
- typeof作為類型操作符后面只能跟變量。
- 如果變量沒有聲明類型,typeof返回變量的推斷類型。
如果變量沒有明確聲明類型,typeof 將返回變量的推斷類型。此時,let關鍵字聲明的變量,可以被重新賦值。
有時候,我們希望變量是常量,不允許被重新賦值。const 關鍵字可以解決這個問題。此時,基于類型推斷,返回類型是等號右邊的字面量類型。
例如,下面示例中,typeof str 返回的是字面量類型 'Echo',不是字符串。
在 Typescript3.4 中引入了一種新的字面量構造方式,const 斷言。在 const 斷言作用下,即使是 let 聲明也可以限制類型擴展,變量不能被重新賦值。
例如,下面示例中,typeof str 返回的是字面量類型 'Echo',不是字符串。
當我們使用 const 斷言構造新的字面量表達式時,應注意以下幾點:
- 表達式中的任何字面量類型都不應該被擴展。
- 對象字面量的屬性,將使用 readonly 修飾。
- 數組字面量將變成 readonly 元組。
如果變量明確聲明了類型,推斷類型不受 const 影響,typeof str 返回 str 的聲明類型 string,而不是字面量類型 "Steven",但是變量依然不能被重新賦值。
typeof與對象結合使用,可以用來獲取對象的結構類型,以及使用該類型來聲明新的變量或函數參數等。
- 獲取對象的類型
在上述示例中,typeof person 返回的是對象 person 的類型,即 { name: string; age: number; }。
- 聲明新變量的類型為對象的類型
在上述示例中,我們使用 typeof person 將 newPerson 的類型聲明為 { name: string; age: number; },并賦予了新的值。
- 在函數參數中使用對象的類型
在上述示例中,函數 printObj 接收一個參數,其類型為 typeof person,即接收與對象 person 相同類型的參數。
需要注意的是,typeof 運算符用于獲取對象類型是在靜態類型檢查階段進行的,而不是在運行時期執行的。因此,它只提供了類型信息,而不會直接訪問對象的值。
2.3. typeof與接口結合使用typeof 與接口結合使用可以用于創建新類型,該類型的屬性和方法將與給定對象類型保持一致。
在上述實例中,定義了一個名為 Person 的接口,然后創建一個對象 person,類型為 Person,接著使用 typeof 來創建一個新的類型 NewPerson,該類型的屬性和方法將與 Person 接口中定義的屬性和方法保持一致,這樣我們就可以基于 NewPerson 來創建新的對象。
需要注意的是,typeof 運算符與接口結合使用通常適用于已存在的對象,它提取已知對象的類型用于創建新的類型。它不會用于動態創建對象或實例化類。
2.4. typeof與keyof結合使用keyof 主要用于獲取類型的所有屬性鍵,可以與 typeof 結合使用,獲取某個類型的鍵集合。
在 TS 中,in 操作符的主要用于遍歷目標類型的屬性 key 值。類似 for...in,一般結合 [] 一起使用。
3.1. 遍歷枚舉類型TypeScript 2.8引入了條件類型表達式,類似于三元運算符。
條件類型也支持嵌套。
泛型工具類型這一章節相關的內容我想放到其它文章中來講,因為這里涉及到的知識點有點多,一時半會寫不完,大家可以持續關注我,精力有限,盡量做到每周2-3更!!!
十五、總結如果文章有什么錯誤,歡迎大家在評論區指正,如果覺得本文對您有幫助的話,歡迎關注點贊收藏哦~
原文鏈接:https://juejin.cn/post/7276630249548005415
下一篇:空調f2故障(如何自行修復)
-
Hidros空調廠家保養加氟(如何正確進行Hidros空調廠家的保養和加氟服務?)
2024-08-25
-
CITEC空調廠家售后網點(如何找到CITEC空調廠家的官方售后維修服務網點?)
2024-08-25
-
有圖像無伴音故障部件(解決有圖像無伴音故障的部件問題:一步步排查與修復
2024-08-25
-
五洲空調售后維修網點地址(如何找到五洲空調售后維修網點的詳細地址?)
2024-08-25
-
山特空調廠家維修服務部(如何獲取山特空調廠家維修服務部的詳細信息和優質
2024-08-25