可選屬性 有時我們希望不要完全匹配一個形狀,那麼可以用可選屬性 (?
):
1 2 3 4 5 6 7 8 interface Person { name : string ; age?: number ; } let tom : Person = { name : 'Tom' };
仍然不允許新增未定義的屬性
任意屬性 有時候我們希望一個介面允許有任意的屬性,可以使用如下方式:
使用 [propName: string]
定義了任意屬性取 string 型別的值。
1 2 3 4 5 6 7 8 9 10 interface Person { name : string ; age?: number ; [propName : string ]: any ; } let tom : Person = { name : 'Tom' , gender : 'male' };
需要注意的是,一旦定義了任意屬性,那麼確定屬性和可選屬性的型別都必須是它的型別的子集 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Person { name : string ; age?: number ; [propName : string ]: string ; } let tom : Person = { name : 'Tom' , age : 25 , gender : 'male' };
簡單來說,可選屬性的型別要跟任意屬性的型別一樣
唯讀屬性 有時候我們希望物件中的一些欄位只能在建立的時候被賦值,那麼可以用 readonly 定義唯讀屬性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 interface Person { readonly id : number ; name : string ; age?: number ; [propName : string ]: any ; } let tom : Person = { id : 89757 , name : 'Tom' , gender : 'male' }; ------------------ tom.id = 9527 ; ------------------ let tony : Person = { name : 'Tony' , gender : 'male' }; tony.id = 89757 ;
注意:唯讀的約束存在於第一次給「物件」賦值的時候,而不是第一次給「唯讀屬性」賦值的時候
陣列的型別 最簡單的方法是使用「型別 + 方括號」來表示陣列:
1 let fibonacci : number [] = [1 , 1 , 2 , 3 , 5 ];
陣列的項中不允許 出現其他的型別:
1 2 3 let fibonacci : number [] = [1 , '1' , 2 , 3 , 5 ];
陣列的一些方法的引數也會根據陣列在定義時約定的型別進行限制:
1 2 3 4 let fibonacci : number [] = [1 , 1 , 2 , 3 , 5 ];fibonacci.push ('8' );
我們也可以使用陣列泛型(Array Generic) Array<elemType>
來表示陣列:
1 let fibonacci : Array <number > = [1 , 1 , 2 , 3 , 5 ];
1 2 3 4 5 interface NumberArray { [index : number ]: number ; } let fibonacci : NumberArray = [1 , 1 , 2 , 3 , 5 ];
雖然介面也可以用來描述陣列,但是我們一般不會這麼做,因為這種方式比前兩種方式複雜多了。
不過有一種情況例外,那就是它常用來表示類別陣列。
類別陣列(Array-like Object)不是陣列型別,比如 arguments:
1 2 3 4 5 function sum ( ) { let args : number [] = arguments ; }
上例中,arguments 實際上是一個類別陣列,不能用普通的陣列的方式來描述,而應該用介面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function sum ( ) { let args : { [index : number ]: number ; length : number ; callee : Function ; } = arguments ; } ------------ interface IArguments { [index : number ]: any ; length : number ; callee : Function ; }
在這個例子中,我們除了約束當索引的型別是數字時,值的型別必須是數字之外,也約束了它還有 length 和 callee 兩個屬性
事實上常用的類別陣列都有自己的介面定義,如 IArguments
, NodeList
, HTMLCollection
等:
1 2 3 function sum ( ) { let args : IArguments = arguments ; }
1 let list : any [] = ['xcatliu' , 25 , { website : 'http://xcatliu.com' }];
函式的型別
函式宣告(Function Declaration)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function sum (x, y ) { return x + y; } let mySum = function (x, y ) { return x + y; }; ---------------- function sum (x: number , y: number ): number { return x + y; } ---------------- sum (1 , 2 , 3 );sum (1 );
注意,輸入多餘的(或者少於要求的)引數,是不被允許的
函式表示式(Function Expression)
1 2 3 4 5 6 7 8 9 10 11 12 let mySum = function (x: number , y: number ): number { return x + y; }; --------------------- let mySum : (x: number , y: number ) => number = function (x: number , y: number ): number { return x + y; };
注意不要混淆了 TypeScript 中的 =>
和 ES6 中的 =>
。
TypeScript 中的 =>
用來表示函數型別,描述函數的參數和返回值
1 2 3 4 5 6 7 8 interface SearchFunc { (source : string , subString : string ): boolean ; } let mySearch : SearchFunc ;mySearch = function (source: string , subString: string ) { return source.search (subString) !== -1 ; }
1 2 3 4 5 6 7 8 9 10 11 function buildName (firstName?: string , lastName: string ) { if (firstName) { return firstName + ' ' + lastName; } else { return lastName; } } let tomcat = buildName ('Tom' , 'Cat' );let tom = buildName (undefined , 'Tom' );
可選引數後面不允許再出現必需引數了
不受「可選引數必須接在必需引數後面」的限制
1 2 3 4 5 function buildName (firstName: string = 'Tom' , lastName: string ) { return firstName + ' ' + lastName; } let tomcat = buildName ('Tom' , 'Cat' );let cat = buildName (undefined , 'Cat' );
ES6 中,可以使用 …rest 的方式獲取函式中的剩餘引數(rest 引數):
1 2 3 4 5 6 7 8 9 function push (array, ...items ) { items.forEach (function (item ) { array.push (item); }); } let a = [];push (a, 1 , 2 , 3 );
事實上,items 是一個數組。所以我們可以用陣列的型別來定義它:
1 2 3 4 5 6 7 8 9 function push (array: any [], ...items: any [] ) { items.forEach (function (item ) { array.push (item); }); } let a = [];push (a, 1 , 2 , 3 );
注意,rest 引數只能是最後一個引數,關於 rest 引數,可以參考 ES6 中的 rest 引數 。
過載 過載允許一個函式接受不同數量或型別的引數時,作出不同的處理。
比如,我們需要實現一個函式 reverse,輸入數字 123 的時候,輸出反轉的數字 321,輸入字串 ‘hello’ 的時候,輸出反轉的字串 ‘olleh’。
利用聯合型別,我們可以這麼實現:
1 2 3 4 5 6 7 function reverse (x: number | string ): number | string { if (typeof x === 'number' ) { return Number (x.toString ().split ('' ).reverse ().join ('' )); } else if (typeof x === 'string' ) { return x.split ('' ).reverse ().join ('' ); } }
然而這樣有一個缺點,就是不能夠精確的表達,輸入為數字的時候,輸出也應該為數字,輸入為字串的時候,輸出也應該為字串。
這時,我們可以使用過載定義多個 reverse 的函式型別:
1 2 3 4 5 6 7 8 9 function reverse (x: number ): number ;function reverse (x: string ): string ;function reverse (x: number | string ): number | string { if (typeof x === 'number' ) { return Number (x.toString ().split ('' ).reverse ().join ('' )); } else if (typeof x === 'string' ) { return x.split ('' ).reverse ().join ('' ); } }
上例中,我們重複定義了多次函式 reverse,前幾次都是函式定義,最後一次是函式實現。在編輯器的程式碼提示中,可以正確的看到前兩個提示。
注意 TypeScript 會優先從最前面的函式定義開始匹配,所以多個函式定義如果有包含關係,需要優先把精確的定義寫在前面
宣告檔案 TypeScript 新手指南 - 宣告檔案
內建物件 TypeScript 新手指南 - 內建物件
進階 型別別名 型別別名用來給一個型別起個新名字。
型別別名常用於聯合型別。
1 2 3 4 5 6 7 8 9 10 11 ype Name = string ; type NameResolver = () => string ;type NameOrResolver = Name | NameResolver ;function getName (n: NameOrResolver ): Name { if (typeof n === 'string' ) { return n; } else { return n (); } }
字串字面量型別 字串字面量 (String Literal) 型別用來約束取值只能是某幾個字串中的一個。
使用 type
定了一個字串字面量型別 EventNames
,它只能取三種字串中的一種 :
1 2 3 4 5 6 7 8 9 type EventNames = 'click' | 'scroll' | 'mousemove' ;function handleEvent (ele: Element, event: EventNames ) { } handleEvent (document .getElementById ('hello' ), 'scroll' ); handleEvent (document .getElementById ('world' ), 'dbclick' );
注意,型別別名與字串字面量型別都是使用 type 進行定義。
元組 數組合併了相同型別的物件,而元組(Tuple)合併了不同型別的物件。
元組是一種特殊的陣列,它的元素型別和順序是固定的。
陣列通常是相同型別的元素集合,且數量和順序可以變動。
1 let tom : [string , number ] = ['Tom' , 25 ];
當賦值或訪問一個已知索引的元素時,會得到正確的型別:
1 2 3 4 5 6 let tom : [string , number ];tom[0 ] = 'Tom' ; tom[1 ] = 25 ; tom[0 ].slice (1 ); tom[1 ].toFixed (2 );
也可以只賦值其中一項:
1 2 let tom : [string , number ];tom[0 ] = 'Tom' ;
但是當直接對元組型別的變數進行初始化或者賦值的時候,需要提供所有元組型別中指定的項。
1 2 3 4 5 6 7 8 9 10 11 let tom : [string , number ];tom = ['Tom' , 25 ]; ---------------------- let tom : [string , number ];tom = ['Tom' ];
越界的元素 如果要在元組要多新增元素時,新增元素的型別會被限制為元組中的每個型別的聯合型別。
1 2 3 4 5 6 7 8 let tom : [string , number ];tom = ['Tom' , 25 ]; tom.push ('male' ); tom.push (true );
枚舉 / 列舉 (Enum) 用於取值被限定在一定範圍內的場景,比如一週只能有七天,顏色限定為紅綠藍等。
列舉使用 enum
關鍵字來定義:
1 enum Days {Sun , Mon , Tue , Wed , Thu , Fri , Sat };
列舉成員會被賦值為從 0
開始遞增的數字,同時也會對列舉值到列舉名進行反向對映:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 enum Days {Sun , Mon , Tue , Wed , Thu , Fri , Sat };console .log (Days ["Sun" ] === 0 ); console .log (Days ["Mon" ] === 1 ); console .log (Days ["Tue" ] === 2 ); console .log (Days ["Sat" ] === 6 ); console .log (Days [0 ] === "Sun" ); console .log (Days [1 ] === "Mon" ); console .log (Days [2 ] === "Tue" ); console .log (Days [6 ] === "Sat" ); -------------------- var Days ;(function (Days ) { Days [Days ["Sun" ] = 0 ] = "Sun" ; Days [Days ["Mon" ] = 1 ] = "Mon" ; Days [Days ["Tue" ] = 2 ] = "Tue" ; Days [Days ["Wed" ] = 3 ] = "Wed" ; Days [Days ["Thu" ] = 4 ] = "Thu" ; Days [Days ["Fri" ] = 5 ] = "Fri" ; Days [Days ["Sat" ] = 6 ] = "Sat" ; })(Days || (Days = {}));
手動賦值 1 2 3 4 5 6 7 enum Days {Sun = 7 , Mon = 1 , Tue , Wed , Thu , Fri , Sat };console .log (Days ["Sun" ] === 7 ); console .log (Days ["Mon" ] === 1 ); console .log (Days ["Tue" ] === 2 ); console .log (Days ["Sat" ] === 6 );
重複賦值 :loop: 如果未手動賦值的列舉項與手動賦值的重複了,TypeScript 是不會察覺到這一點的
手動賦值的列舉項也可以為小數
或負數
,此時後續未手動賦值的項的遞增步長仍為 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 enum Days {Sun = 3 , Mon = 1 , Tue , Wed , Thu , Fri , Sat };console .log (Days ["Sun" ] === 3 ); console .log (Days ["Wed" ] === 3 ); console .log (Days [3 ] === "Sun" ); console .log (Days [3 ] === "Wed" ); -------------------- var Days ;(function (Days ) { Days [Days ["Sun" ] = 3 ] = "Sun" ; Days [Days ["Mon" ] = 1 ] = "Mon" ; Days [Days ["Tue" ] = 2 ] = "Tue" ; Days [Days ["Wed" ] = 3 ] = "Wed" ; Days [Days ["Thu" ] = 4 ] = "Thu" ; Days [Days ["Fri" ] = 5 ] = "Fri" ; Days [Days ["Sat" ] = 6 ] = "Sat" ; })(Days || (Days = {}));
使用的時候需要注意,最好不要出現這種覆蓋的情況。
常數項(constant member)和計算所得項(computed member) 計算所得項的例子:
1 2 3 4 5 6 7 8 9 10 enum Color {Red , Green , Blue = "blue" .length };---------------- enum Color {Red = "red" .length , Green , Blue };
如果緊接在計算所得項後面的是未手動賦值的項,那麼它就會因為無法獲得初始值而報錯
當滿足以下條件時,列舉成員被當作是常數 :
類別 傳統方法中,JavaScript 透過建構函式實現類別的概念,透過原型鏈實現繼承。而在 ES6 中,我們終於迎來了 class
。( MDN - 繼承與原型鏈 )
TypeScript 除了實現了所有 ES6 中的類別的功能以外,還添加了一些新的用法。
類別的概念
類別 ( Class ):類別是一種藍圖,定義了某個事物的屬性和行為(方法)。 比如,如果我們有一個「車子」的類別,那麼這個類別會包含「車子」的一些特徵(顏色、型號)以及它能做的事情(開動、停下)。
物件(Object):物件是類別的具體實例。 你可以把類別想像成一個模型,而物件就是根據這個模型創建出來的具體事物。比如,你可以用 new 關鍵字來根據「車子」的類別創建出一個具體的「我的車子」物件。
面向物件(OOP)的三大特性:封裝 、繼承 、多型
封裝(Encapsulation):將內部的資料和行為隱藏起來 ,只暴露給外部使用的方法。這樣可以保護資料不被隨意修改。比如車子的引擎工作細節是封裝起來的,我們只需要按下「啟動」按鈕就能開車,無需知道內部運作。
繼承(Inheritance):子類別可以繼承父類別的特性和行為 ,並且還可以擴展 或修改 這些行為。比如,「電動車」類別可以繼承「車子」類別的所有功能,並增加「充電」的方法。
多型(Polymorphism):子類別可以根據自己的特性,實現不同版本的相同行為 。比如「貓」和「狗」都繼承了「動物」類別,但它們各自有不同的 eat() 方法,適合自己吃東西的方式。
存取器(getter & setter):存取器用來控制屬性值的讀取和設定 行為。你可以定義一個 getter 來控制物件屬性如何被讀取,或用 setter 來控制如何更改屬性值。
修飾符(Modifiers):修飾符用來限制類別中屬性或方法的存取範圍。
常見的修飾符有:
public:屬性或方法可以被任何人存取
private:屬性或方法只能在類別內部使用,外部無法存取
protected:屬性或方法可以在子類別中使用,但外部無法直接存取
抽象類別(Abstract Class):抽象類別是一個不能直接創建實例 的類別,必須由其他類別繼承。它通常用來定義一些基本的行為,但具體的實作必須由子類別來完成。像是「動物」類別可以是抽象的,然後「貓」和「狗」類別來實現具體的 eat() 行為。
介面(Interfaces):不同類別之間公有的屬性或方法 ,可以抽象成一個介面。介面可以被類別實現(implements)。
一個類別只能繼承自另一個類別,但是可以實現多個介面
總結 這些概念能幫助我們將程式分成小的、易於理解的部分。
類別提供了結構,封裝保護了資料。
繼承與多型提供了彈性。
存取器與修飾符則進一步控制程式行為。
抽象類別與介面讓我們能夠建立清晰的設計規範。
TypeScript 中類別的用法 public private 和 protected TypeScript 可以使用三種訪問修飾符(Access Modifiers),分別是 public、private 和 protected。
public 修飾的屬性或方法是公有的,可以在任何地方被訪問到,預設所有的屬性和方法都是 public 的
private 修飾的屬性或方法是私有的,不能在宣告它的類別的外部訪問
protected 修飾的屬性或方法是受保護的,它和 private 類似,區別是它在子類別中也是允許被訪問的
當建構函式修飾為 private 時,該類別不允許被繼承或者實例化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Animal { public name; private constructor (name) { this .name = name; } } class Cat extends Animal { constructor (name) { super (name); } } let a = new Animal ('Jack' );
當建構函式修飾為 protected 時,該類別只允許被繼承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Animal { public name; protected constructor (name) { this .name = name; } } class Cat extends Animal { constructor (name) { super (name); } } let a = new Animal ('Jack' );
修飾符還可以使用在建構函式引數中,等同於類別中定義該屬性,使程式碼更簡潔 :
1 2 3 4 5 6 class Animal { public constructor (public name) { this .name = name; } }
readonly 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Animal { readonly name; public constructor (name ) { this .name = name; } } let a = new Animal ('Jack' );console .log (a.name ); a.name = 'Tom' ; -------------------- class Animal { public constructor (public readonly name ) { this .name = name; } }
注意如果 readonly 和其他訪問修飾符同時存在的話,需要寫在其後面。
抽象類別 abstract
用於定義抽象類別和其中的抽象方法。
抽象類別是不允許被實例化的:
1 2 3 4 5 6 7 8 9 10 11 abstract class Animal { public name; public constructor (name ) { this .name = name; } public abstract sayHi (); } let a = new Animal ('Jack' );
抽象類別中的抽象方法必須被子類別實現:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 abstract class Animal { public name; public constructor (name ) { this .name = name; } public abstract sayHi (); } class Cat extends Animal { public eat ( ) { console .log (`${this .name} is eating.` ); } } let cat = new Cat ('Tom' );--------------------- abstract class Animal { public name; public constructor (name ) { this .name = name; } public abstract sayHi (); } class Cat extends Animal { public sayHi ( ) { console .log (`Meow, My name is ${this .name} ` ); } } let cat = new Cat ('Tom' );
類別與介面 TypeScript 新手指南 - 類別與介面
泛型 泛型(Generics)是指在定義函式、介面或類別的時候,不預先指定具體的型別,而在使用的時候再指定型別的一種特性。
Array<any>
允許陣列的每一項都為任意型別。但是我們預期的是,陣列中每一項都應該是輸入的 value 的型別。
這時候,泛型就派上用場了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function createArray (length: number , value: any ): Array <any > { let result = []; for (let i = 0 ; i < length; i++) { result[i] = value; } return result; } createArray (3 , 'x' ); ---------------------- function createArray<T>(length : number , value : T): Array <T> { let result : T[] = []; for (let i = 0 ; i < length; i++) { result[i] = value; } return result; } createArray<string >(3 , 'x' );
多個型別引數 定義泛型的時候,可以一次定義多個型別引數:
1 2 3 4 5 function swap<T, U>(tuple : [T, U]): [U, T] { return [tuple[1 ], tuple[0 ]]; } swap ([7 , 'seven' ]);
泛型約束 在函式內部使用泛型變數的時候,由於事先不知道它是哪種型別,所以不能隨意的操作它的屬性或方法:
1 2 3 4 5 6 7 function loggingIdentity<T>(arg : T): T { console .log (arg.length ); return arg; }
這時,我們可以對泛型進行約束,只允許這個函式傳入那些包含 length 屬性的變數。這就是泛型約束:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface Lengthwise { length : number ; } function loggingIdentity<T extends Lengthwise >(arg : T): T { console .log (arg.length ); return arg; } ------------------- function copyFields<T extends U, U>(target : T, source : U): T { for (let id in source) { target[id] = (<T>source)[id]; } return target; } let x = { a : 1 , b : 2 , c : 3 , d : 4 };copyFields (x, { b : 10 , d : 20 });
泛型介面 TypeScript 新手指南 - 泛型介面
泛型類別 TypeScript 新手指南 - 泛型類別
宣告合併 TypeScript 新手指南 - 宣告合併