鍍金池/ 教程/ HTML/ 介紹
初始化項(xiàng)目結(jié)構(gòu)
聯(lián)合類型
介紹
介紹
介紹
編譯選項(xiàng)
TypeScript 1.6
介紹
介紹
發(fā)展路線圖
介紹
在MSBuild里使用編譯選項(xiàng)
可迭代性
TypeScript 1.3
介紹
介紹
TypeScript 1.1
變量聲明
即將到來(lái)的Angular 2框架是使用TypeScript開(kāi)發(fā)的。 因此Angular和TypeScript一起使用非常簡(jiǎn)單方便
tsconfig.json
介紹
介紹
介紹
在MSBuild里使用編譯選項(xiàng)
使用TypeScript的每日構(gòu)建版本
新建工程
枚舉
三斜線指令
結(jié)合ASP.NET v5使用TypeScript
TypeScript里的this
介紹
TypeScript 1.4
編碼規(guī)范
介紹
模塊解析
ASP.NET 4
架構(gòu)概述
介紹
介紹
ASP.NET Core
TypeScript 1.8
介紹
介紹
創(chuàng)建簡(jiǎn)單工程
TypeScript 1.7
TypeScript 1.5
NPM包的類型
支持TypeScript的編輯器

介紹

TypeScript里的類型兼容性是基于結(jié)構(gòu)子類型的。 結(jié)構(gòu)類型是一種只使用其成員來(lái)描述類型的方式。 它正好與名義(nominal)類型形成對(duì)比。(譯者注:在基于名義類型的類型系統(tǒng)中,數(shù)據(jù)類型的兼容性或等價(jià)性是通過(guò)明確的聲明和/或類型的名稱來(lái)決定的。這與結(jié)構(gòu)性類型系統(tǒng)不同,它是基于類型的組成結(jié)構(gòu),且不要求明確地聲明。) 看下面的例子:

interface Named {
    name: string;
}

class Person {
    name: string;
}

let p: Named;
// OK, because of structural typing
p = new Person();

在使用基于名義類型的語(yǔ)言,比如C#或Java中,這段代碼會(huì)報(bào)錯(cuò),因?yàn)镻erson類沒(méi)有明確說(shuō)明其實(shí)現(xiàn)了Named接口。

TypeScript的結(jié)構(gòu)性子類型是根據(jù)JavaScript代碼的典型寫(xiě)法來(lái)設(shè)計(jì)的。 因?yàn)镴avaScript里廣泛地使用匿名對(duì)象,例如函數(shù)表達(dá)式和對(duì)象字面量,所以使用結(jié)構(gòu)類型系統(tǒng)來(lái)描述這些類型比使用名義類型系統(tǒng)更好。

關(guān)于可靠性的注意事項(xiàng)

TypeScript的類型系統(tǒng)允許某些在編譯階段無(wú)法確認(rèn)其安全性的操作。當(dāng)一個(gè)類型系統(tǒng)具此屬性時(shí),被當(dāng)做是“不可靠”的。TypeScript允許這種不可靠行為的發(fā)生是經(jīng)過(guò)仔細(xì)考慮的。通過(guò)這篇文章,我們會(huì)解釋什么時(shí)候會(huì)發(fā)生這種情況和其有利的一面。

開(kāi)始

TypeScript結(jié)構(gòu)化類型系統(tǒng)的基本規(guī)則是,如果x要兼容y,那么y至少具有與x相同的屬性。比如:

interface Named {
    name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: 'Alice', location: 'Seattle' };
x = y;

這里要檢查y是否能賦值給x,編譯器檢查x中的每個(gè)屬性,看是否能在y中也找到對(duì)應(yīng)屬性。 在這個(gè)例子中,y必須包含名字是namestring類型成員。y滿足條件,因此賦值正確。

檢查函數(shù)參數(shù)時(shí)使用相同的規(guī)則:

function greet(n: Named) {
    alert('Hello, ' + n.name);
}
greet(y); // OK

注意,y有個(gè)額外的location屬性,但這不會(huì)引發(fā)錯(cuò)誤。 只有目標(biāo)類型(這里是Named)的成員會(huì)被一一檢查是否兼容。

這個(gè)比較過(guò)程是遞歸進(jìn)行的,檢查每個(gè)成員及子成員。

比較兩個(gè)函數(shù)

相對(duì)來(lái)講,在比較原始類型和對(duì)象類型的時(shí)候是比較容易理解的,問(wèn)題是如何判斷兩個(gè)函數(shù)是兼容的。 下面我們從兩個(gè)簡(jiǎn)單的函數(shù)入手,它們僅是參數(shù)列表略有不同:

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error

要查看x是否能賦值給y,首先看它們的參數(shù)列表。 x的每個(gè)參數(shù)必須能在y里找到對(duì)應(yīng)類型的參數(shù)。 注意的是參數(shù)的名字相同與否無(wú)所謂,只看它們的類型。 這里,x的每個(gè)參數(shù)在y中都能找到對(duì)應(yīng)的參數(shù),所以允許賦值。

第二個(gè)賦值錯(cuò)誤,因?yàn)?code>y有個(gè)必需的第二個(gè)參數(shù),但是x并沒(méi)有,所以不允許賦值。

你可能會(huì)疑惑為什么允許忽略參數(shù),像例子y = x中那樣。 原因是忽略額外的參數(shù)在JavaScript里是很常見(jiàn)的。 例如,Array#forEach給回調(diào)函數(shù)傳3個(gè)參數(shù):數(shù)組元素,索引和整個(gè)數(shù)組。 盡管如此,傳入一個(gè)只使用第一個(gè)參數(shù)的回調(diào)函數(shù)也是很有用的:

let items = [1, 2, 3];

// Don't force these extra arguments
items.forEach((item, index, array) => console.log(item));

// Should be OK!
items.forEach((item) => console.log(item));

下面來(lái)看看如何處理返回值類型,創(chuàng)建兩個(gè)僅是返回值類型不同的函數(shù):

let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});

x = y; // OK
y = x; // Error because x() lacks a location property

類型系統(tǒng)強(qiáng)制源函數(shù)的返回值類型必須是目標(biāo)函數(shù)返回值類型的子類型。

函數(shù)參數(shù)雙向協(xié)變

當(dāng)比較函數(shù)參數(shù)類型時(shí),只有當(dāng)源函數(shù)參數(shù)能夠賦值給目標(biāo)函數(shù)或者反過(guò)來(lái)時(shí)才能賦值成功。 這是不穩(wěn)定的,因?yàn)檎{(diào)用者可能傳入了一個(gè)具有更精確類型信息的函數(shù),但是調(diào)用這個(gè)傳入的函數(shù)的時(shí)候卻使用了不是那么精確的類型信息。 實(shí)際上,這極少會(huì)發(fā)生錯(cuò)誤,并且能夠?qū)崿F(xiàn)很多JavaScript里的常見(jiàn)模式。例如:

enum EventType { Mouse, Keyboard }

interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }

function listenEvent(eventType: EventType, handler: (n: Event) => void) {
    /* ... */
}

// Unsound, but useful and common
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));

// Undesirable alternatives in presence of soundness
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));

// Still disallowed (clear error). Type safety enforced for wholly incompatible types
listenEvent(EventType.Mouse, (e: number) => console.log(e));

可選參數(shù)及剩余參數(shù)

比較函數(shù)兼容性的時(shí)候,可選參數(shù)與必須參數(shù)是可交換的。 原類型上額外的可選參數(shù)并不會(huì)造成錯(cuò)誤,目標(biāo)類型的可選參數(shù)沒(méi)有對(duì)應(yīng)的參數(shù)也不是錯(cuò)誤。

當(dāng)一個(gè)函數(shù)有剩余參數(shù)時(shí),它被當(dāng)做無(wú)限個(gè)可選參數(shù)。

這對(duì)于類型系統(tǒng)來(lái)說(shuō)是不穩(wěn)定的,但從運(yùn)行時(shí)的角度來(lái)看,可選參數(shù)一般來(lái)說(shuō)是不強(qiáng)制的,因?yàn)閷?duì)于大多數(shù)函數(shù)來(lái)說(shuō)相當(dāng)于傳遞了一些undefinded。

有一個(gè)好的例子,常見(jiàn)的函數(shù)接收一個(gè)回調(diào)函數(shù)并用對(duì)于程序員來(lái)說(shuō)是可預(yù)知的參數(shù)但對(duì)類型系統(tǒng)來(lái)說(shuō)是不確定的參數(shù)來(lái)調(diào)用:

function invokeLater(args: any[], callback: (...args: any[]) => void) {
    /* ... Invoke callback with 'args' ... */
}

// Unsound - invokeLater "might" provide any number of arguments
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));

// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));

函數(shù)重載

對(duì)于有重載的函數(shù),源函數(shù)的每個(gè)重載都要在目標(biāo)函數(shù)上找到對(duì)應(yīng)的函數(shù)簽名。 這確保了目標(biāo)函數(shù)可以在所有源函數(shù)可調(diào)用的地方調(diào)用。

枚舉

枚舉類型與數(shù)字類型兼容,并且數(shù)字類型與枚舉類型兼容。不同枚舉類型之間是不兼容的。比如,

enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };

let status = Status.Ready;
status = Color.Green;  //error

類與對(duì)象字面量和接口差不多,但有一點(diǎn)不同:類有靜態(tài)部分和實(shí)例部分的類型。 比較兩個(gè)類類型的對(duì)象時(shí),只有實(shí)例的成員會(huì)被比較。 靜態(tài)成員和構(gòu)造函數(shù)不在比較的范圍內(nèi)。

class Animal {
    feet: number;
    constructor(name: string, numFeet: number) { }
}

class Size {
    feet: number;
    constructor(numFeet: number) { }
}

let a: Animal;
let s: Size;

a = s;  //OK
s = a;  //OK

類的私有成員

私有成員會(huì)影響兼容性判斷。 當(dāng)類的實(shí)例用來(lái)檢查兼容時(shí),如果它包含一個(gè)私有成員,那么目標(biāo)類型必須包含來(lái)自同一個(gè)類的這個(gè)私有成員。 這允許子類賦值給父類,但是不能賦值給其它有同樣類型的類。

泛型

因?yàn)門(mén)ypeScript是結(jié)構(gòu)性的類型系統(tǒng),類型參數(shù)只影響使用其做為類型一部分的結(jié)果類型。比如,

interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;

x = y;  // okay, y matches structure of x

上面代碼里,xy是兼容的,因?yàn)樗鼈兊慕Y(jié)構(gòu)使用類型參數(shù)時(shí)并沒(méi)有什么不同。 把這個(gè)例子改變一下,增加一個(gè)成員,就能看出是如何工作的了:

interface NotEmpty<T> {
    data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;

x = y;  // error, x and y are not compatible

在這里,泛型類型在使用時(shí)就好比不是一個(gè)泛型類型。

對(duì)于沒(méi)指定泛型類型的泛型參數(shù)時(shí),會(huì)把所有泛型參數(shù)當(dāng)成any比較。 然后用結(jié)果類型進(jìn)行比較,就像上面第一個(gè)例子。

比如,

let identity = function<T>(x: T): T {
    // ...
}

let reverse = function<U>(y: U): U {
    // ...
}

identity = reverse;  // Okay because (x: any)=>any matches (y: any)=>any

高級(jí)主題

子類型與賦值

目前為止,我們使用了兼容性,它在語(yǔ)言規(guī)范里沒(méi)有定義。 在TypeScript里,有兩種類型的兼容性:子類型與賦值。 它們的不同點(diǎn)在于,賦值擴(kuò)展了子類型兼容,允許給any賦值或從any取值和允許數(shù)字賦值給枚舉類型或枚舉類型賦值給數(shù)字。

語(yǔ)言里的不同地方分別使用了它們之中的機(jī)制。 實(shí)際上,類型兼容性是由賦值兼容性來(lái)控制的甚至在implementsextends語(yǔ)句里。 更多信息,請(qǐng)參閱TypeScript語(yǔ)言規(guī)范.

上一篇:TypeScript 1.6下一篇:介紹