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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM