TypeScript不僅支持JavaScript所包含的數據類型,還額外擴展了許多實用的數據類型,例如枚舉、空值、任意值等。
一、JavaScript的數據類型
JavaScript的數據類型包括6種基本類型:undefined、null、布爾值、數字、字符串以及ES6新增的Symbol,還有1種復雜類型:object。由於TypeScript提供了可選的靜態類型聲明(即在變量后跟一個冒號和類型聲明),因此同樣的變量聲明,在TypeScript中將更能傳播代碼的意圖,並且在編譯時還能驗證代碼的正確性。在下面的代碼中,聲明了6種類型(不包括Symbol),並且引入了ES6新增的二進制、八進制和模板字面量。
let isDone: boolean = false; let decimal: number = 10; //十進制 let hex: number = 0xa; //十六進制 let binary: number = 0b1010; //二進制 let octal: number = 0o12; //八進制 let name: string = "strick"; let template: string = `my name is ${name}`; //模板字面量 let u: undefined = undefined; let n: null = null; let obj: object = {};
二、TypeScript擴展的類型
1)任意值
當變量聲明為任意值(any類型)時,它在編譯階段會跨過類型檢查,並且能被賦為任意類型的值,還允許訪問任意屬性、調用任意方法。在下面的示例中,雖然能編譯通過,但是在使用時卻會拋出錯誤,因為字符串類型的變量沒有toFixed()方法。
let data: any = 10.5; data = "strick"; data.toFixed(); //錯誤
注意,沒有顯式聲明類型的變量,默認都是any類型的。
2)空值
在JavaScript中,沒有空值(void類型)的概念。TypeScript中的void不表示任意類型,與any類型相悖。當一個函數沒有返回值時,通常會將其返回值的類型聲明成void,如下所示。
function send(): void { }
如果聲明一個void類型的變量,那么它的值只能是undefined或null,如下所示。
let u: void = undefined; let n: void = null;
3)Never
never類型表示那些永不存在的值的類型,例如包含死循環不會有返回值的函數或拋出錯誤的函數,如下所示。
function loop(): never { while (true) {} } function error(message: string): never { throw new Error(message); }
注意,當一個函數沒有返回值時,它返回一個void類型;而當函數被意外中斷時,它返回一個never類型。
由於never類型是所有類型的子類型,因此可被賦給任意類型。但是除了其自身之外,其它類型(包括any)都不能賦給它,如下所示。
let none: never; let digit: number = none; //正確 let figure: never = 10; //錯誤
4)數組
在TypeScript中,有兩種常見的數組聲明方式。第一種通過類型和方括號組合來表示數組,第二種是使用數組泛型,如下所示。
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
在指定了元素類型之后,就不能添加其它類型的元素,例如為數組的push()方法傳入一個字符串數字,如下所示,在編譯時會報錯。
arr1.push("4");
如果數組需要包含各種類型的元素,那么可以將其聲明成any類型,如下所示。
let arr3: any[] = [1, "2", true];
5)元組
在TypeScript中,元組(Tuple)會合並不同類型的值,例如定義一對string和number兩種類型的元組,如下所示。
let list: [string, number];
當為元組賦值時,需要指定相應類型的元素,即先傳字符串,后傳數字,下面代碼的第二次賦值沒有按照這個順序,因此在編譯時將報錯。
list = ["strick", 10]; //正確 list = [10, "strick"]; //錯誤
也可以通過索引為元組添加元素,但也要遵守類型限制,如下所示。
list[0] = "strick";
list[1] = 10;
當添加越界的元素時,只要該元素是元組的聯合類型(既可以是字符串,也可以是數字),就能編譯成功,如下所示。
list.push(10); //正確 list.push(true); //錯誤
在編譯第二條語句時,會報“Argument of type 'true' is not assignable to parameter of type 'string | number'.”的錯誤。
三、枚舉
枚舉是一個被命名的常量集合,該類型也是對JavaScript標准數據類型的一個補充。和C#、Java等其它語言一樣,使用枚舉可以更清晰的表達代碼意圖。TypeScript支持數字的和字符串兩種類型的枚舉。
1)數字枚舉
默認情況下,定義的都是數字枚舉,如下所示,其中枚舉成員的值從0開始自增長,例如Up的值為0,Down的值為1,其余依次遞增。
enum Direction { Up, Down, Left, Right }
通過枚舉名可以正向映射得到枚舉值,而通過枚舉值也可以反向映射得到枚舉名,如下所示。
Direction.Up; //0 Direction[0]; //"Up"
由於TypeScript中的枚舉會被編譯成下面這樣,因此才能通過兩種映射方式分別得到枚舉名和枚舉值。
var Direction; (function (Direction) { Direction[Direction["Up"] = 0] = "Up"; Direction[Direction["Down"] = 1] = "Down"; Direction[Direction["Left"] = 2] = "Left"; Direction[Direction["Right"] = 3] = "Right"; })(Direction || (Direction = {}));
數字枚舉也支持手動賦值,例如將上例中的Direction從1開始遞增,如下所示,Down的值為2。注意,定義的數字還可以是小數、負數等各種與數字兼容的值。
enum Direction { Up = 1, Down, Left, Right }
或者也可以為每個枚舉成員都賦值,如下所示。
enum Direction { Up = 1, Down = 3, Left = 5, Right = 7 }
2)字符串枚舉
在字符串枚舉中,每個成員的值都得是字符串類型的,如下所示。
enum Color { Red = "RED", Green = "GREEN", Blue = "BLUE" }
字符串枚舉提供了有意義、可調試的字符串,常用於簡單的值比較,如下所示。
if (colorName === Color.Red) { console.log("success"); }
3)異構枚舉
枚舉還可以混合字符串和數字兩種成員,如下所示。
enum Mix { Up = 1, Red = "RED" }
4)枚舉成員
每個枚舉成員都會包含一個值,而根據值的來源可將成員分成常量成員(Constant Member)和計算成員(Computed Member)。
之前示例中的枚舉成員都是常量,除此之外,當枚舉成員通過常量枚舉表達式初始化時,也會成為常量成員。常量枚舉表達式是TypeScript表達式的子集,可在編譯階段求值。當一個表達式滿足下面一個條件時(引用自官方文檔),它就是一個常量枚舉表達式:
(1)枚舉表達式字面量,例如字符串字面量或數字字面量。
(2)一個對之前定義的常量成員的引用,可以在不同的枚舉類型中。
(3)帶括號的常量枚舉表達式。
(4)將+、-、~三個一元運算符中的一個應用到常量枚舉表達式中。
(5)常量枚舉表達式作為二元運算符+、-、*、/、%、<<、>>、>>>、&、|或^的操作對象。
enum Con { Red = 1, Green = Direction.Down, Blue = -(1 << 1), Yellow = Red & Blue }
不滿足上述條件的枚舉成員會被當作計算成員來使用,並且要注意,計算成員之后,都需要手動賦值,否則會在編譯階段報錯。下面這個枚舉就會編譯失敗。
enum Computed { Red = "red".length, Green, Blue }
5)常量枚舉
在聲明枚舉時添加const關鍵字就能生成常量枚舉,如下所示。
const enum Colors { Red, Green, Blue }
常量枚舉只能使用常量枚舉表達式,不能包含計算成員,並且會在編譯階段被刪除,其成員在被引用到時才會被內聯進來,例如將上例Colors中的成員組成一個數組,其編譯結果如下所示。
let colors = [Colors.Red, Colors.Green, Colors.Blue]; //編譯結果 var colors = [0 /* Red */, 1 /* Green */, 2 /* Blue */];
6)外部枚舉
在聲明枚舉時添加declare關鍵字就能生成外部枚舉,即全局枚舉,如下所示。
declare enum Externals { Red, Green, Blue }
外部枚舉用於描述已經存在的枚舉類型,並且在編譯結果中會移除declare聲明的枚舉,例如將Externals的成員組成一個數組。
let externals = [Externals.Red, Externals.Green, Externals.Blue];
在運行時調用externals時,如果沒有定義Externals對象,那么就會報錯。
declare還可以與其它關鍵字(例如var、function、class等)配合,聲明全局變量、全局函數、全局類等。
四、類型斷言
類型斷言可指定一個值的類型,類似於類型轉換,但它只在編譯階段起作用,並且不影響運行時的結果。類型斷言包含兩種語法形式,第一種是尖括號語法,第二種是as語法,如下所示。當在TypeScript中使用JSX時,只支持as語法。
let age: number = 28; let digit = <number>age; //語法一 let figure = age as number; //語法二
五、聯合類型
聯合類型(Union Type)可讓一個變量擁有多種類型,在語法上,通過豎線(|)來分隔每個類型,例如下面的data變量,既可以是字符串,也可以是數字。
let data: number | string; data = 10; data = "strick";
注意,當訪問聯合類型的成員時,只能訪問它們共有的成員。以上面示例的data變量為例,它能成功調用toString()方法,但不能訪問length屬性(如下所示),因為length屬性只存在於string中,而toString()方法是兩者共有的。
data.toString(); //正確 data.length; //錯誤
六、函數
TypeScript中的函數不僅包含ES6的默認參數、剩余參數等功能,還新增了許多額外的功能,例如類型聲明、重載等。
1)函數創建
在創建函數時可以為其參數和返回值添加類型,從而起到約束的作用。在下面的示例中,通過兩種方式創建函數,第一種是函數聲明,第二種是函數表達式。
function add(x: number, y: number): number { //第一種 return x + y; } let minus = function(x: number, y: number): number { //第二種 return x - y; };
由於TypeScript能根據return語句推斷出返回值的類型,因此可以省略該類型的聲明。注意,如果在調用函數時傳遞多余的參數,那么在編譯時就會報錯。
function add(x: number, y: number) { //正確 return x + y; } add(1, 2, 3);
TypeScript還可以為函數表達式右側的變量添加類型,如下所示,其中“=>”符號不表示箭頭函數,而是用來定義函數的返回值類型,並且返回值類型必須指定。
let minus: (x: number, y: number) => number = function(x: number, y: number): number { return x - y; }; //正確 let minus: (x: number, y: number) = function(x: number, y: number): number { return x - y; }; //錯誤
注意,兩處的參數名稱可以不一致,只要類型匹配即可,如下所示。
let minus: (left: number, right: number) => number = function(x: number, y: number): number { return x - y; }; //正確
2)可選參數
在JavaScript中,函數的參數都是可選的,而在TypeScript中的參數默認都是必傳的。如果要讓參數可選,那么需要在其后面跟一個問號(?),如下所示。
function sum(x: number, y?: number): number { return x + y; }
注意,可選參數得位於必選參數之后,下面的寫法是錯誤的。
function sum(x?: number, y: number): number { return x + y; }
3)重載
JavaScript里的函數可根據不同數量和類型的參數返回不同類型的值,這樣雖然很便捷,但是無法精確的傳達出函數的輸入和輸出之間的對應關系。TypeScript提供了重載功能,可有效改善JavaScript函數定義不明確的問題。以重載定義多個caculate()函數為例,如下所示。
function caculate(x: number, y: number): number; function caculate(x: string, y: string): string; function caculate(x, y): any { return x + y; }
編譯器會從重載列表中選出最先匹配的函數定義,並進行正確的類型檢查。當多個函數定義之間是包含關系時,優先把最精確的定義放在最前面。
在調用改變后的caculate()函數時(如下所示),傳遞給它的實參,其類型和組合只要能與重載列表中的一個相同,就能編譯成功,否則就會報錯。
caculate(1, 2); //正確 caculate("1", "2"); //正確 caculate(false, true); //錯誤 caculate("1", 2); //錯誤
4)this參數
TypeScript能在定義函數時,顯式地聲明一個this參數,指定this的類型(即限制其指向),從而避免錯誤的使用this。例如為this定義為void類型,那么在函數中一旦使用this,就無法編譯成功,如下所示。
function func(this: void) { this.name = "strick"; //錯誤 }
注意,this是個假參數,位於參數列表的最前面,只用來做靜態檢查,不會出現在編譯后的代碼中。