在像C#和Java這樣的語言中,可使用泛型來建立可重用的組件,一個組件能夠支持多種類型的數據。 這樣用戶就能夠以本身的數據類型來使用組件。算法
咱們須要一種方法使返回值的類型與傳入參數的類型是相同的。 這裏,咱們使用了 類型變量,它是一種特殊的變量,只用於表示類型而不是值。數組
function identity<T>(arg: T): T {
return arg;
}
複製代碼
咱們給identity添加了類型變量T。 T幫助咱們捕獲用戶傳入的類型(好比:number),以後咱們就可使用這個類型。 以後咱們再次使用了 T當作返回值類型。如今咱們能夠知道參數類型與返回值類型是相同的了。 這容許咱們跟蹤函數裏使用的類型的信息。bash
咱們把這個版本的identity函數叫作泛型,由於它能夠適用於多個類型。傳入數值類型並返回數值類型。app
function generics_func1<T>(arg: T): T {
return arg;
}
// 或者
let generics_func2: <T>(arg: T) => T = function (arg) {
return arg;
}
複製代碼
咱們定義了泛型函數後,能夠用兩種方法使用。ide
第一種是,傳入全部的參數,包含類型參數:函數
let output = identity<string>("myString"); // type of output will be 'string'
複製代碼
第二種方法更廣泛。利用了類型推論 -- 能夠省略類型參數,由於編譯器會根據傳入參數來自動識別對應的類型。ui
let output = identity("myString"); // type of output will be 'string'
複製代碼
在方法一的方法體裏,打印了arg參數的length屬性。由於any能夠代替任意類型,因此該方法在傳入參數不是數組或者帶有length屬性對象時,會拋出異常。而方法二定義了參數類型是Array的泛型類型,確定會有length屬性,因此不會拋出異常。可是若是不加array或[]也沒有length屬性。this
從下面這個例子能夠看出,泛型類型相比較any類型來講,在某些狀況下會帶有類型自己的一些信息,而any類型則沒有。spa
// 方法一:帶有any參數的方法
function any_func(arg: any): any {
console.log(arg.length);
return arg;
}
// 方法二:Array泛型方法
function array_func<T>(arg: Array<T>): Array<T> {
console.log(arg.length);
return arg;
}
複製代碼
泛型接口調試
interface Generics_interface<T> {
(arg: T): T;
}
function func_demo<T>(arg: T): T {
return arg;
}
let func1: Generics_interface<number> = func_demo;
func1(123); // 正確類型的實際參數
func1('123'); // 錯誤類型的實際參數(由於已經定義爲number了)
複製代碼
經過在接口上聲明泛型,聲明變量時明確指定泛型的具體類型,則賦值的方法將自動帶上具體的類型約束。
泛型類型繼承
interface LengthInterface {
length: number;
}
function func_demo<T extends LengthInterface>(arg: T): T {
console.log(arg.length);
return arg;
}
func_demo({ a: 1, length: 2 }); // 含有length屬性的對象
func_demo([1, 2]); // 數組類型
複製代碼
上面的例子裏,泛型類型繼承自一個擁有length屬性成員的接口,泛型類型將自動加上length屬性的約束。調用時只有符合條件的對象才能正確賦值。
function copy<T extends U, U>(source: U, target: T): T {
for (let prop in source) {
target[prop] = source[prop];
}
return target;
}
copy({ a: 1, b: 2 }, { a: 2, b: 3, c: 4 }); // 正確的實際參數
copy({ a: 1, b: 2 }, { q: 2, c: 4 }); // 錯誤的實際參數
複製代碼
在上面的例子裏,一個泛型類型繼承自另一個泛型類型。在方法調用時,就必須確保繼承類型對應的參數對象屬性徹底包含被繼承類型對應的參數對象。
class Generics_Demo<T>{
value: T;
show(): T {
return this.value;
}
}
let gene_demo1 = new Generics_Demo<number>();
gene_demo1.value = 1;
console.log(gene_demo1.show()); // 調用方法
gene_demo1.show = function () { return gene_demo1.value + 1; } // 賦值新方法,返回值類型必須是number
console.log(gene_demo1.show());
複製代碼
經過指定明確類型的泛型類的實例,對屬性賦值時,必須知足實際類型的約束。
enum Direction {
Up = 1,
Down,
Left,
Right
}
複製代碼
如上,咱們定義了一個數字枚舉, Up使用初始化爲 1。 其他的成員會從 1開始自動增加。換句話說, Direction.Up的值爲 1, Down爲 2, Left爲 3, Right爲 4若是不定義初始值的話就是從0開始。
使用枚舉很簡單:經過枚舉的屬性來訪問枚舉成員,和枚舉的名字來訪問枚舉類型:
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Princess Caroline", Response.Yes)
複製代碼
下面的狀況是不被容許的:
enum E {
A = getSomeValue(),
B, // error! 'A' is not constant-initialized, so 'B' needs an initializer
}
複製代碼
在一個字符串枚舉裏,每一個成員都必須用字符串字面量,或另一個字符串枚舉成員進行初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
複製代碼
因爲字符串枚舉沒有自增加的行爲,字符串枚舉能夠很好的序列化。 換句話說,若是你正在調試而且必需要讀一個數字枚舉的運行時的值,這個值一般是很難讀的 - 它並不能表達有用的信息,字符串枚舉容許你提供一個運行時有意義的而且可讀的值,獨立於枚舉成員的名字。
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
複製代碼
從技術的角度來講,枚舉能夠混合字符串和數字成員,可是咱們不建議這樣作
當知足以下條件時,枚舉成員被看成是常量:
一、它是枚舉的第一個成員且沒有初始化器,這種狀況下它被賦予值 0:
// E.X is constant:
enum E { X }
複製代碼
二、它不帶有初始化器且它以前的枚舉成員是一個 數字常量。 這種狀況下,當前枚舉成員的值爲它上一個枚舉成員的值加1。
// All enum members in 'E1' and 'E2' are constant.
enum E1 { X, Y, Z }
enum E2 {
A = 1, B, C
}
複製代碼
三、枚舉成員使用 常量枚舉表達式初始化。常數枚舉表達式是TypeScript表達式的子集,它能夠在編譯階段求值。當一個表達式知足下面條件之一時,它就是一個常量枚舉表達式:
一個枚舉表達式字面量(主要是字符串字面量或數字字面量)
一個對以前定義的常量枚舉成員的引用(能夠是在不一樣的枚舉類型中定義的)
帶括號的常量枚舉表達式
一元運算符 +, -, ~其中之一應用在了常量枚舉表達式
常量枚舉表達式作爲二元運算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操做對象。 若常數枚舉表達式求值後爲 NaN或Infinity,則會在編譯階段報錯。
全部其它狀況的枚舉成員被看成是須要計算得出的值。
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length
}
複製代碼
當須要從幾個表達式中推斷類型時候,例如
let x = [0, 1, null];
複製代碼
爲了推斷x的類型,咱們必須考慮全部元素的類型。 這裏有兩種選擇: number和null。 計算通用類型算法會考慮全部的候選類型,並給出一個兼容全部候選類型的類型。
模塊在其自身的做用域裏執行,而不是在全局做用域裏;
這意味着定義在一個模塊裏的變量,函數,類等等在模塊外部是不可見的,除非你明確地使用export之一導出它們。
相反,若是想使用其它模塊導出的變量,函數,類,接口等的時候,你必需要導入它們,可使用import之一。
模塊是自聲明的。在TypeScript裏,兩個模塊之間的關係是經過在文件級別上使用import和export創建的。下面是一個基本例子:
animal.ts
1 export class Animal {
2 name: string;
3 show(): string {
4 return this.name;
5 }
6 }
複製代碼
app.ts
1 import {Animal} from './animal';
2 let dog = new Animal();
3 dog.name = '狗狗';
4 dog.show();
複製代碼
上面的例子裏,在animal.ts裏聲明瞭一個類Animal,經過export導出。在app.ts裏,指定相對文件路徑,經過import導入,就可使用Animal類。
導入和導出時,經過as關鍵字對模塊進行重命名。 animal.ts
class Animal {
name: string;
show(): string {
return this.name;
}
}
export {Animal as ANI};
複製代碼
app.ts
import {ANI as Animal} from './animal';
let dog = new Animal();
dog.name = '狗狗';
dog.show();
複製代碼
MyLargeModule.ts
export class Dog { ... }
export class Cat { ... }
export class Tree { ... }
export class Flower { ... }
複製代碼
Consumer.ts
import * as myLargeModule from "./MyLargeModule.ts";
let x = new myLargeModule.Dog();
複製代碼