1. 程式人生 > >TypeScript躬行記(1)——資料型別

TypeScript躬行記(1)——資料型別

  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是個假引數,位於引數列表的最前面,只用來做靜態檢查,不會出現在編譯後的程式碼