本文為系列文章《TypeScript 簡明教程》中的一篇。
從這篇文章,我們開始講解 TypeScript 的型別系統。之前我們說到,TypeScript 是 JavaScript 的超集,是 JavaScript 的繼承與發展,即所謂的『增強版』。這一點,從 TypeScript 中的基本型別可以看出。TS 的資料型別與 JS 幾乎相同,此外還額外提供一些資料型別方便我們使用。
首先,我們來看 JavaScript 中 6 種原始資料型別在 TypeScript 中的使用,它們分別為:number
、string
、boolean
、null
、undefined
、symbol
。
原始資料型別
number
型別
TypeScript 中,使用 number
表示變數為數值型別。與 JavaScript 一致,TypeScript 中所有數值都是浮點數,支援二進位制、八進位制、十進位制和十六進位制四種表示方法。
let decimalNumber: number = 42
let hexNumber: number = 0x2A
let binaryNumber: number = 0b101010
let octalNumber: number = 0o52
複製程式碼
二進位制和八進位制表示採用 ES6 新的寫法,分別使用字首 `0b`(或 `0B`)和 `0o`(或 `0O` 英文字母 O)表示。從 ES5 開始,不再允許使用字首 `0` 表示八進位制。如果 `tsconfig` 中 target 高於 ES5 版本,TypeScript 將會出現提示:禁止使用字首 `0` 表示八進位制。
除了浮點數以外,JavaScript 中還有一組特別的數值:Infinity
、-Infinity
、NaN
,它們也屬於 number
型別。
const positiveLargestNumber: number = Infinity
const negativeLargestNumber: number = -Infinity
const notANumber: number = NaN
複製程式碼
Infinity
即正無窮,用以表示超出 JS 所能表示的最大值的數值,不同環境下這個值有所不同,可通過 Number.MAX_VALUE
檢視,一般為 1.7976931348623157E+308
。與之相對應的是 -Infinity
,表示負無窮。
注意區分**負無窮**和**無窮小**兩個概念。負無窮表示比任何數都小的一個負數值,無窮小表示以 0 為極限,無限趨於零的一個正數值。JS 中的無窮小值可通過 `Number.MIN_VALUE` 檢視,一般為 `5E-324`。
NaN
表示一個本應返回數值的運算元未返回數值的情況。比如:
let newNumber: number = Number('a') // => NaN
複製程式碼
更多內容可以參考:ECMAScript 6 入門 - 數值的擴充套件
string
型別
和其他程式語言一樣,字串是必不可少的一種型別,用於表示文字資料。可以通過 string
宣告某個變數為字串型別。
const theAnswer: number = 42
let sentence: string = `The Answer to the Ultimate Question of Life, The Universe, and Everything is ${ theAnswer }.`
複製程式碼
TypeScript 中你依舊可以使用雙引號("
)和單引號('
)來表示字串。除此以外,你還可以使用 ES6 提供的 模板字串 語法。TypeScript 會自動將其編譯為字串拼接的形式。
// 編譯結果
var theAnswer = 42;
var sentence = "The Answer to the Ultimate Question of Life, The Universe, and Everything is " + theAnswer + ".";
複製程式碼
boolean
型別
布林值可以說是最基本的資料型別了,它的可能取值只有兩個:true
、false
。TypeScript 中使用 boolean
表示布林值型別。
let num: number = -10
let isPositive: boolean = num > 0
複製程式碼
symbol
型別
symbol
是 ES6 新增的資料型別,用於表示獨一無二的值,只能 Symbol
函式生成。更多內容可以參考:ECMAScript 6 入門 - Symbol。
const sym: symbol = Symbol()
複製程式碼
事實上,TypeScript 官網上並沒有詳細介紹 symbol
這一基礎資料型別。想來原因有二:
symbol
值只能通過Symbol()
函式生成,由於型別推斷(後面會講)的存在,無需特別宣告變數為symbol
型別。symbol
的唯一性依賴於底層實現,不太好做 polyfill,所以對於編譯 target 低於 ES6 版本的情況,並不推薦使用symbol
。
不過,出於完整性的考慮,這裡還是簡要提一下。
undefined
和 null
型別
JavaScript 中,undefined
和 null
都被用來表示空值。作為兩個原始資料型別,undefined
型別的變數只能被賦值為 undefined
,null
型別的變數只能被賦值為 null
。
// 一般來說,直接定義一個 undefined 或者 null 型別的變數並沒有太大意義
let u: undefined = undefined
let n: null = null
複製程式碼
預設情況下,undefined
和 null
是所有其他型別的子型別。也就是說,如果 undefined
和 null
賦值給任何其他型別的變數。
例如:
let person: string = 'Jerry'
person = null // 有效的
複製程式碼
然而,如果在 tsconfig
中開啟了 strictNullChecks
,那麼 undefined
和 null
就只能賦值給 void
或 any
型別變數以及它們自身型別的變數。強烈建議開啟這一選項,它能幫助避免很多常見的問題。
undefined
和 null
的關係
儘管 undefined
和 null
都表示空值,但是它們本質上是不同的。undefined
表示變數已宣告但未初始化,null
則在邏輯上表示一個空物件指標(這也正是 typeof null === 'object'
的原因)。
無論什麼情況下,都沒有必要把一個變數的值顯式地設定為
undefined
,可是同樣的規則對null
卻不適用。換句話說,只要意在儲存物件的變數還沒有真正儲存物件,就應該明確地讓該變數儲存null
值。 —— Nicholas C.Zakas 《JavaScript 高階程式設計》
高階型別
void
型別
void
型別表示沒有任何型別,這句話聽起來可能有些自相矛盾。不過,讓我們來考慮這樣一種情況:如果一個函式沒有返回值,我們如何表示其型別?這時候 void
型別就派上用場了。這裡和 C 語言很像。
function warning(): void {
console.log('WARNING!')
}
複製程式碼
當然,這裡你也可以指定返回值型別為
undefined
。因為 JS 中,如果函式沒有返回值,則會預設返回undefind
。不過,使用void
型別可以使表意更清晰。
然而,宣告一個 void
型別的變數並沒有太大的用處,因為你只能將其賦值為 null
和 undefined
。
請注意與
void
運算子區分。void
運算子的作用是:對給定的表示式進行求值,然後返回undefined
。如果你從事前端多年,你或許會比較熟悉javascript:void(0);
這樣的程式碼。感興趣的可以看 這裡。
any
型別
某種程度上,any
型別就像是 void
型別的反面,任何型別都是 any
型別的子型別。換句話說,any
型別的變數可以被賦予任何型別的值。
let anything: any = "Ohh, that's crazy"
anything = 42
複製程式碼
對於 any
型別,有兩個需要注意的點。
首先,如果變數在宣告時,沒有宣告其型別,也沒有初始化(因為型別推斷會自動判斷型別),那麼它會被當做 any
型別。
let something // 等價於 let something: any;
something = 'autochess'
something = 6
複製程式碼
其次,在 any
型別變數上可以訪問任何屬性,即使它並不存在。
let something: any = 42
something.mayExist() // 沒問題,因為其可能在執行時存在
something.toFixed() // 沒問題,雖然確實存在,但是編譯器並不會去檢查
複製程式碼
事實上,any
型別是 TypeScript 提供的一個『跳出』方案。對於 any
型別變數,編譯器不會做任何型別檢查,直接讓它們通過型別檢查。這在引入第三方庫以及將原有 JS 程式碼改寫為 TS 時尤其有用。
不過,注意不要濫用 any
型別。錯誤地濫用 any
型別會讓 TS 失去其存在的意義。
never
型別
never
用於表示永遠不會存在的值的型別。never
是任何型別的子型別,但沒有型別是 never
的子型別。
對於 never
型別,新手理解起來可能比較困難。但是對於 TypeScript 這樣一個分析程式碼流的語言來說,never
型別是理所當然且必要的。
never
型別常用於兩種情況:
- 用於描述從不會有返回值的函式
- 用於描述總是丟擲錯誤的函式
// 第一種情況
function neverStop(): never {
while (true) {
// do something
}
}
// 第二種情況
function error(message: string): never {
throw new Error(message)
}
複製程式碼
對於上述兩種情況,函式的返回型別都是 never
。
never
型別也可以用做變數的型別註解(例如:let foo: never;
),但是這樣做並沒有什麼意義。
下面我們通過一個例子來學習 never
型別的用法:
function checkNumber(x: string | number): boolean {
if (typeof x === 'number') {
return true
} else if (typeof x === 'string') {
return false
}
return fail('Failure')
}
function fail(message: string): never {
throw new Error(message)
}
複製程式碼
對於上面的 checkNumber
函式,如果沒有 return fail('Failure')
這一句的話,TypeScript 會報錯,因為不是所有條件語句都有返回值,當引數 x
既不是 number
也不是 string
時,函式會預設返回 undefined
,然而在嚴格模式下,undefined
型別與 boolean
型別並不相容。但是由於 fail
函式返回的 never
型別是 boolean
的子型別,所有上述寫法可以通過編譯。這也是 never
必須是任何型別的子型別的原因。
關於 never
型別,這裡只是舉個例子幫助大家理解。想要真正理解 never
型別,還得結合實戰仔細揣摩才行。
總結
本篇文章主要介紹了 TypeScript 中的原始資料型別和幾個高階型別。通過這篇文章,相信大家已經可以看出 TypeScript 型別系統的強大和完備。
當然這還不是全部,下篇文章我們將介紹 TypeScript 中其他幾個高階型別:Object
、Array
、Tuple
和 Enum
。敬請期待~