一官地、簡介
1.1 什么是 TypeScript
-
TypeScript
是JavaScript
的一個超集,主要提供了類型系統(tǒng)和對ES6
的支持 -
TypeScript
是由微軟開發(fā)的一款開源的編程語言 -
TypeScript
是Javascript
的超級,遵循最新的ES6
、Es5
規(guī)范精居。TypeScript
擴展了JavaScript
的語法 -
TypeScript
更像后端java
、C#
這樣的面向?qū)ο笳Z言可以讓js
開發(fā)大型企業(yè)項目
1.2 為什么選擇 TypeScript
Typescript
和es6
潜必、es5
關(guān)系
TypeScript 增加了代碼的可讀性和可維護性
- 類型系統(tǒng)實際上是最好的文檔靴姿,大部分的函數(shù)看看類型的定義就可以知道如何使用了
- 可以在編譯階段就發(fā)現(xiàn)大部分錯誤,這總比在運行時候出錯好
- 增強了編輯器和
IDE
的功能磁滚,包括代碼補全佛吓、接口提示、跳轉(zhuǎn)到定義垂攘、重構(gòu)等
TypeScript 非常包容
-
TypeScript
是JavaScript
的超集维雇,.js
文件可以直接重命名為.ts
即可 - 即使不顯式的定義類型,也能夠自動做出類型推論
- 可以定義從簡單到復(fù)雜的幾乎一切類型
- 即使
TypeScript
編譯報錯搜贤,也可以生成JavaScript
文件 - 兼容第三方庫谆沃,即使第三方庫不是用
TypeScript
寫的,也可以編寫單獨的類型文件供TypeScript
讀取
TypeScript 擁有活躍的社區(qū)
- 大部分第三方庫都有提供給
TypeScript
的類型定義文件 -
Google
開發(fā)的Angular2
就是使用TypeScript
編寫的 -
TypeScript
擁抱了ES6
規(guī)范仪芒,也支持部分ESNext
草案的規(guī)范 - 最新的
Vue
唁影、React
也可以集成TypeScript
TypeScript 的缺點
- 有一定的學(xué)習(xí)成本耕陷,需要理解接口(
Interfaces
)、泛型(Generics
)据沈、類(Classes
)哟沫、枚舉類型(Enums
)等前端工程師可能不是很熟悉的概念 - 短期可能會增加一些開發(fā)成本讼积,畢竟要多寫一些類型的定義沽损,不過對于一個需要長期維護的項目,
TypeScript
能夠減少其維護成本 - 集成到構(gòu)建流程需要一些工作量
- 可能和一些庫結(jié)合的不是很完美
1.3 安裝 TypeScript
typescript 安裝
npm i typescript -g
全局安裝完成后炬太,我們新建一個
hello.ts
的ts
文件
// hello.ts內(nèi)容
let a = "poet"
接下來我們在命令行輸入
tsc hello.ts
來編譯這個ts
文件孔祸,然后會在同級目錄生成一個編譯好了的hello.js
文件
// hello.js內(nèi)容
var = "poet"
那么我們每次都要輸
tsc hello.ts
命令來編譯隆敢,這樣很麻煩,能否讓它自動編譯崔慧?答案是可以的拂蝎,使用vscode
來開發(fā),需要配置一下vscode
就可以惶室。
首先我們在命令行執(zhí)行
tsc --init
來生成配置文件温自,然后我們在目錄下看到生成了一個tsconfig.json
文件
這個
json
文件里有很多選項
-
target
是選擇編譯到什么語法 -
module
則是模塊類型 -
outDir
則是輸出目錄,可以指定這個參數(shù)到指定目錄
更多細節(jié) https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/tsconfig.json.html
1.4 Hello TypeScript
將以下代碼復(fù)制到
hello.ts
中
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = 'poetries';
console.log(sayHello(user));
tsc hello.ts
//這時候會生成一個編譯好的文件 hello.js:
function sayHello(person) {
return 'Hello, ' + person;
}
var user = 'poetries';
console.log(sayHello(user));
TypeScript
中皇钞,使用:
指定變量的類型悼泌,:
的前后有沒有空格都可以
-
TypeScript
只會進行靜態(tài)檢查,如果發(fā)現(xiàn)有錯誤夹界,編譯的時候就會報錯 -
TypeScript
編譯的時候即使報錯了馆里,還是會生成編譯結(jié)果,我們?nèi)匀豢梢允褂眠@個編譯之后的文件
二掉盅、基礎(chǔ)
2.1 原始數(shù)據(jù)類型
JavaScript
的類型分為兩種:原始數(shù)據(jù)類型(Primitive data types
)和對象類型(Object types
)也拜。
- 原始數(shù)據(jù)類型包括:
布爾值
、數(shù)值
趾痘、字符串
慢哈、null
、undefined
以及ES6
中的新類型Symbol
永票。
本節(jié)主要介紹前五種原始數(shù)據(jù)類型在
TypeScript
中的應(yīng)用
2.1.1 布爾值
布爾值是最基礎(chǔ)的數(shù)據(jù)類型卵贱,在
TypeScript
中,使用boolean
定義布爾值類型
let isDone: boolean = false;
// 編譯通過
// 后面約定侣集,未強調(diào)編譯錯誤的代碼片段键俱,默認為編譯通過
注意,使用構(gòu)造函數(shù)
Boolean
創(chuàng)造的對象不是布爾值
let createdByNewBoolean: boolean = new Boolean(1);
// index.ts(1,5): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
// 后面約定世分,注釋中標(biāo)出了編譯報錯的代碼片段编振,表示編譯未通過
- 事實上
new Boolean()
返回的是一個Boolean
對象:
let createdByNewBoolean: Boolean = new Boolean(1);
- 直接調(diào)用
Boolean
也可以返回一個boolean
類型:
let createdByBoolean: boolean = Boolean(1);
- 在
TypeScript
中,boolean
是JavaScript
中的基本類型臭埋,而Boolean
是JavaScript
中的構(gòu)造函數(shù)踪央。其他基本類型(除了null
和undefined
)一樣
2.1.2 數(shù)值
使用
number
定義數(shù)值類型
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二進制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八進制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
//編譯結(jié)果:
var decLiteral = 6;
var hexLiteral = 0xf00d;
// ES6 中的二進制表示法
var binaryLiteral = 10;
// ES6 中的八進制表示法
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;
其中
0b101
0 和0o744
是ES6
中的二進制和八進制表示法臀玄,它們會被編譯為十進制數(shù)字
2.1.3 字符串
使用
string
定義字符串類型:
let myName: string = 'Tom';
let myAge: number = 25;
// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
2.1.4 空值
JavaScript
沒有空值(Void
)的概念,在TypeScript
中畅蹂,可以用void
表示沒有任何返回值的函數(shù)
function alertName(): void {
alert('My name is Tom');
}
聲明一個
void
類型的變量沒有什么用健无,因為你只能將它賦值為undefined
和null
:
let unusable: void = undefined;
2.1.5 Null 和 Undefined
在
TypeScript
中,可以使用null
和undefined
來定義這兩個原始數(shù)據(jù)類型:
let u: undefined = undefined;
let n: null = null;
undefined
類型的變量只能被賦值為undefined
液斜,null
類型的變量只能被賦值為null
- 與
void
的區(qū)別是累贤,undefined
和null
是所有類型的子類型。也就是說undefined
類型的變量少漆,可以賦值給number
類型的變量
// 這樣不會報錯
let num: number = undefined;
// 這樣也不會報錯
let u: undefined;
let num: number = u;
而
void
類型的變量不能賦值給number
類型的變量:
let u: void;
let num: number = u;
// index.ts(2,5): error TS2322: Type 'void' is not assignable to type 'number'.
2.2 任意值A(chǔ)ny
如果是一個普通類型臼膏,在賦值過程中改變類型是不被允許的
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
但如果是
any
類型,則允許被賦值為任意類型示损。
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
任意值的屬性和方法
在任意值上訪問任何屬性都是允許的:
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
也允許調(diào)用任何方法:
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
可以認為讶请,聲明一個變量為任意值之后,對它的任何操作屎媳,返回的內(nèi)容的類型都是任意值
未聲明類型的變量
變量如果在聲明的時候,未指定其類型论巍,那么它會被識別為任意值類型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
等價于
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
2.3 類型推論
如果沒有明確的指定類型烛谊,那么
TypeScript
會依照類型推論(Type Inference
)的規(guī)則推斷出一個類型
什么是類型推論
以下代碼雖然沒有指定類型,但是會在編譯的時候報錯:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
事實上嘉汰,它等價于:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
TypeScript
會在沒有明確的指定類型的時候推測出一個類型丹禀,這就是類型推論
如果定義的時候沒有賦值,不管之后有沒有賦值鞋怀,都會被推斷成 any 類型而完全不被類型檢查
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
2.4 聯(lián)合類型
聯(lián)合類型(
Union Types
)表示取值可以為多種類型中的一種
// 簡單例子
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
let myFavoriteNumber: string | number;
myFavoriteNumber = true;
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
- 聯(lián)合類型使用
|
分隔每個類型双泪。 - 這里的
let myFavoriteNumber: string | number
的含義是,允許myFavoriteNumber
的類型是string
或者number
密似,但是不能是其他類型
訪問聯(lián)合類型的屬性或方法
當(dāng)
TypeScript
不確定一個聯(lián)合類型的變量到底是哪個類型的時候焙矛,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法
function getLength(something: string | number): number {
return something.length;
}
// length 不是 string 和 number 的共有屬性,所以會報錯
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
訪問
string
和number
的共有屬性是沒問題的
function getString(something: string | number): string {
return something.toString();
}
聯(lián)合類型的變量在被賦值的時候残腌,會根據(jù)類型推論的規(guī)則推斷出一個類型
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 編譯時報錯
// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
- 上例中村斟,第二行的
myFavoriteNumber
被推斷成了string
,訪問它的length
屬性不會報錯抛猫。 - 而第四行的
myFavoriteNumber
被推斷成了number
蟆盹,訪問它的length
屬性時就報錯了
2.5 對象的類型——接口
2.5.1 簡單例子
在
TypeScript
中,我們使用接口(Interfaces
)來定義對象的類型
什么是接口
- 在面向?qū)ο笳Z言中闺金,接口(
Interfaces
)是一個很重要的概念逾滥,它是對行為的抽象,而具體如何行動需要由類(classes
)去實現(xiàn)(implements
)败匹。 -
TypeScript
中的接口是一個非常靈活的概念寨昙,除了可用于對類的一部分行為進行抽象以外讥巡,也常用于對「對象的形狀(Shape
)」進行描述。
接口一般首字母大寫
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
上面的例子中毅待,我們定義了一個接口
Person
尚卫,接著定義了一個變量tom
,它的類型是Person
尸红。這樣吱涉,我們就約束了tom
的形狀必須和接口Person
一致
定義的變量比接口少了一些屬性是不允許的
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom'
};
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
多一些屬性也是不允許的
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可見,賦值的時候外里,變量的形狀必須和接口的形狀保持一致怎爵。
2.5.2 可選屬性
有時我們希望不要完全匹配一個形狀,那么可以用可選屬性
可選屬性的含義是該屬性可以不存在
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom'
};
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
2.5.3 任意屬性
有時候我們希望一個接口允許有任意的屬性盅蝗,可以使用如下方式
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
- 使用
[propName: string]
定義了任意屬性取string
類型的值 - 需要注意的是鳖链,一旦定義了任意屬性,那么確定屬性和可選屬性都必須是它的子屬性
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
- 上例中墩莫,任意屬性的值允許是
string
芙委,但是可選屬性age
的值卻是number
,number
不是string
的子屬性狂秦,所以報錯了灌侣。 - 另外,在報錯信息中可以看出裂问,此時
{ name: 'Tom', age: 25, gender: 'male' }
的類型被推斷成了{ [x: string]: string | number; name: string; age: number; gender: string; }
侧啼,這是聯(lián)合類型和接口的結(jié)合
2.5.4 只讀屬性
有時候我們希望對象中的一些字段只能在創(chuàng)建的時候被賦值,那么可以用
readonly
定義只讀屬性
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中堪簿,使用
readonly
定義的屬性id
初始化后痊乾,又被賦值了,所以報錯了
注意椭更,只讀的約束存在于第一次給對象賦值的時候哪审,而不是第一次給只讀屬性賦值的時候
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
tom.id = 89757;
// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
- 上例中,報錯信息有兩處甜孤,第一處是在對
tom
進行賦值的時候协饲,沒有給id
賦值。 - 第二處是在給
tom.id
賦值的時候缴川,由于它是只讀屬性茉稠,所以報錯了
2.6 數(shù)組的類型
在
TypeScript
中,數(shù)組類型有多種定義方式把夸,比較靈活而线。
2.6.1「類型 + 方括號」表示法
最簡單的方法是使用「類型 + 方括號」來表示數(shù)組:
let fibonacci: number[] = [1, 1, 2, 3, 5];
數(shù)組的項中不允許出現(xiàn)其他的類型
let fibonacci: number[] = [1, '1', 2, 3, 5];
// index.ts(1,5): error TS2322: Type '(number | string)[]' is not assignable to type 'number[]'.
// Type 'number | string' is not assignable to type 'number'.
// Type 'string' is not assignable to type 'number'.
- 上例中,
[1, '1', 2, 3, 5]
的類型被推斷為(number | string)[]
,這是聯(lián)合類型和數(shù)組的結(jié)合膀篮。 - 數(shù)組的一些方法的參數(shù)也會根據(jù)數(shù)組在定義時約定的類型進行限制
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
// index.ts(2,16): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
上例中嘹狞,
push
方法只允許傳入number
類型的參數(shù),但是卻傳了一個string
類型的參數(shù)誓竿,所以報錯了
2.6.2 數(shù)組泛型
也可以使用數(shù)組泛型(
Array Generic
)Array<elemType>
來表示數(shù)組
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
2.6.3 用接口表示數(shù)組
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
NumberArray
表示:只要index
的類型是number
磅网,那么值的類型必須是number
2.6.4 any 在數(shù)組中的應(yīng)用
一個比較常見的做法是,用
any
表示數(shù)組中允許出現(xiàn)任意類型:
let list: any[] = ['poetries', 22, { website: 'http://blog.poetries.top' }];
2.6.5 類數(shù)組
類數(shù)組(
Array-like Object
)不是數(shù)組類型筷屡,比如arguments
function sum() {
let args: number[] = arguments;
}
// index.ts(2,7): error TS2322: Type 'IArguments' is not assignable to type 'number[]'.
// Property 'push' is missing in type 'IArguments'.
事實上常見的類數(shù)組都有自己的接口定義涧偷,如
IArguments
,NodeList
,HTMLCollection
等:
function sum() {
let args: IArguments = arguments;
}
2.7 函數(shù)的類型
2.7.1 函數(shù)聲明
在
JavaScript
中,有兩種常見的定義函數(shù)的方式——函數(shù)聲明(Function Declaration
)和函數(shù)表達式(Function Expression
)
// 函數(shù)聲明(Function Declaration)
function sum(x, y) {
return x + y;
}
// 函數(shù)表達式(Function Expression)
let mySum = function (x, y) {
return x + y;
};
一個函數(shù)有輸入和輸出毙死,要在
TypeScript
中對其進行約束燎潮,需要把輸入和輸出都考慮到,其中函數(shù)聲明的類型定義較簡單
function sum(x: number, y: number): number {
return x + y;
}
注意扼倘,輸入多余的(或者少于要求的)參數(shù)确封,是不被允許的:
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2, 3);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
return x + y;
}
sum(1);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
2.7.2 函數(shù)表達式
如果要我們現(xiàn)在寫一個對函數(shù)表達式(
Function Expression
)的定義,可能會寫成這樣
let mySum = function (x: number, y: number): number {
return x + y;
};
這是可以通過編譯的再菊,不過事實上爪喘,上面的代碼只對等號右側(cè)的匿名函數(shù)進行了類型定義,而等號左邊的
mySum
纠拔,是通過賦值操作進行類型推論而推斷出來的腥放。如果需要我們手動給mySum
添加類型,則應(yīng)該是這樣
// =>左邊 (x: number, y: number) 是輸入類型
// =>右邊number是輸出類型
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>
在
TypeScript
的類型定義中绿语,=>
用來表示函數(shù)的定義,左邊是輸入類型候址,需要用括號括起來吕粹,右邊是輸出類型。
2.7.3 用接口定義函數(shù)的形狀
我們也可以使用接口的方式來定義一個函數(shù)需要符合的形狀
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
需要注意的是岗仑,可選參數(shù)必須接在必需參數(shù)后面匹耕。換句話說,可選參數(shù)后面不允許再出現(xiàn)必須參數(shù)了
function buildName(firstName?: string, lastName: string) {
if (firstName) {
return firstName + ' ' + lastName;
} else {
return lastName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');
// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
2.7.4 參數(shù)默認值
在
ES6
中荠雕,我們允許給函數(shù)的參數(shù)添加默認值稳其,TypeScript
會將添加了默認值的參數(shù)識別為可選參數(shù)
function buildName(firstName: string, lastName: string = 'Cat') {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
此時就不受「可選參數(shù)必須接在必需參數(shù)后面」的限制了
function buildName(firstName: string = 'Tom', lastName: string) {
return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let cat = buildName(undefined, 'Cat');
2.7.5 剩余參數(shù)
ES6 中,可以使用
...rest
的方式獲取函數(shù)中的剩余參數(shù)(rest
參數(shù))
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
事實上炸卑,items 是一個數(shù)組既鞠。所以我們可以用數(shù)組的類型來定義它
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3);
注意,rest 參數(shù)只能是最后一個參數(shù)
2.7.6 函數(shù)重載
- 重載允許一個函數(shù)接受不同數(shù)量或類型的參數(shù)時盖文,作出不同的處理嘱蛋。
比如,我們需要實現(xiàn)一個函數(shù)
reverse
,輸入數(shù)字123
的時候洒敏,輸出反轉(zhuǎn)的數(shù)字321
龄恋,輸入字符串'hello'
的時候,輸出反轉(zhuǎn)的字符串'olleh'
利用聯(lián)合類型凶伙,我們可以這么實現(xiàn)
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('');
}
}
然而這樣有一個缺點郭毕,就是不能夠精確的表達,輸入為數(shù)字的時候函荣,輸出也應(yīng)該為數(shù)字显押,輸入為字符串的時候,輸出也應(yīng)該為字符串
這時偏竟,我們可以使用重載定義多個 reverse 的函數(shù)類型
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('');
}
}
- 上例中煮落,我們重復(fù)定義了多次函數(shù)
reverse
,前幾次都是函數(shù)定義踊谋,最后一次是函數(shù)實現(xiàn)蝉仇。在編輯器的代碼提示中,可以正確的看到前兩個提示殖蚕。
注意轿衔,
TypeScript
會優(yōu)先從最前面的函數(shù)定義開始匹配,所以多個函數(shù)定義如果有包含關(guān)系睦疫,需要優(yōu)先把精確的定義寫在前面
2.8 類型斷言
類型斷言(
Type Assertion
)可以用來手動指定一個值的類型害驹。
語法
<類型>值
// 或
值 as 類型
在
tsx
語法(React
的jsx
語法的ts
版)中必須用后一種
例子:將一個聯(lián)合類型的變量指定為一個更加具體的類型
當(dāng) TypeScript 不確定一個聯(lián)合類型的變量到底是哪個類型的時候,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
而有時候蛤育,我們確實需要在還不確定類型的時候就訪問其中一個類型的屬性或方法宛官,比如
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
// index.ts(3,26): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
上例中,獲取
something.length
的時候會報錯
此時可以使用類型斷言瓦糕,將 something 斷言成 string
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
類型斷言的用法如上底洗,在需要斷言的變量前加上
<Type>
即可
類型斷言不是類型轉(zhuǎn)換,斷言成一個聯(lián)合類型中不存在的類型是不允許的
function toBoolean(something: string | number): boolean {
return <boolean>something;
}
// index.ts(2,10): error TS2352: Type 'string | number' cannot be converted to type 'boolean'.
// Type 'number' is not comparable to type 'boolean'.
2.9 聲明文件
當(dāng)使用第三方庫時咕娄,我們需要引用它的聲明文件
2.9.1 聲明(declare)語句
假如我們想使用第三方庫亥揖,比如
jQuery
,我們通常這樣獲取一個id
是foo
的元素
$('#foo');
// or
jQuery('#foo');
但是在
TypeScript
中圣勒,我們并不知道$
或jQuery
是什么東西
jQuery('#foo');
// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.
這時费变,我們需要使用
declare
關(guān)鍵字來定義它的類型,幫助TypeScript
判斷我們傳入的參數(shù)類型對不對
declare var jQuery: (selector: string) => any;
jQuery('#foo');
declare
定義的類型只會用于編譯時的檢查圣贸,編譯結(jié)果中會被刪除
//上例的編譯結(jié)果是:
jQuery('#foo');
2.9.2 聲明文件(約定.d.ts后綴)
通常我們會把類型聲明放到一個單獨的文件中挚歧,這就是聲明文件
// jQuery.d.ts
declare var jQuery: (string) => any;
- 我們約定聲明文件以
.d.ts
為后綴。 - 然后在使用到的文件的開頭吁峻,用
「三斜線指令」///
表示引用了聲明文件
/// <reference path="./jQuery.d.ts" />
jQuery('#foo');
2.9.3 第三方聲明文件
當(dāng)然昼激,
jQuery
的聲明文件不需要我們定義了庇绽,已經(jīng)有人幫我們定義好了:jQuery in DefinitelyTyped
// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/jquery/index.d.ts
// Type definitions for jquery 3.3
// Project: https://jquery.com
// Definitions by: Leonard Thieu <https://github.com/leonard-thieu>
// Boris Yankov <https://github.com/borisyankov>
// Christian Hoffmeister <https://github.com/choffmeister>
// Steve Fenton <https://github.com/Steve-Fenton>
// Diullei Gomes <https://github.com/Diullei>
// Tass Iliopoulos <https://github.com/tasoili>
// Jason Swearingen <https://github.com/jasons-novaleaf>
// Sean Hill <https://github.com/seanski>
// Guus Goossens <https://github.com/Guuz>
// Kelly Summerlin <https://github.com/ksummerlin>
// Basarat Ali Syed <https://github.com/basarat>
// Nicholas Wolverson <https://github.com/nwolverson>
// Derek Cicerone <https://github.com/derekcicerone>
// Andrew Gaspar <https://github.com/AndrewGaspar>
// Seikichi Kondo <https://github.com/seikichi>
// Benjamin Jackman <https://github.com/benjaminjackman>
// Poul Sorensen <https://github.com/s093294>
// Josh Strobl <https://github.com/JoshStrobl>
// John Reilly <https://github.com/johnnyreilly>
// Dick van den Brink <https://github.com/DickvdBrink>
// Thomas Schulz <https://github.com/King2500>
// Terry Mun <https://github.com/terrymun>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.3
// 引入聲明文件
/// <reference types="sizzle" />
/// <reference path="JQueryStatic.d.ts" />
/// <reference path="JQuery.d.ts" />
/// <reference path="misc.d.ts" />
/// <reference path="legacy.d.ts" />
export = jQuery;
- 我們可以直接下載下來使用,但是更推薦的是使用工具統(tǒng)一管理第三方庫的聲明文件- 社區(qū)已經(jīng)有多種方式引入聲明文件橙困,不過
TypeScript 2.0
推薦使用@types
來管理瞧掺。 -
@types
的使用方式很簡單,直接用npm
安裝對應(yīng)的聲明模塊即可凡傅,以jQuery
舉例
npm install @types/jquery --save-dev
可以在這個頁面搜索你需要的聲明文件
2.10 內(nèi)置對象
JavaScript
中有很多內(nèi)置對象辟狈,它們可以直接在TypeScript
中當(dāng)做定義好了的類型
內(nèi)置對象是指根據(jù)標(biāo)準(zhǔn)在全局作用域(
Global
)上存在的對象。這里的標(biāo)準(zhǔn)是指ECMAScript
和其他環(huán)境(比如DOM
)的標(biāo)準(zhǔn)
2.10.1 ECMAScript 的內(nèi)置對象
ECMAScript 標(biāo)準(zhǔn)提供的內(nèi)置對象有
Boolean
夏跷、Error
哼转、Date
、RegExp
等
我們可以在 TypeScript
中將變量定義為這些類型:
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
更多的內(nèi)置對象槽华,可以查看 MDN 的文檔
而他們的定義文件壹蔓,則在 TypeScript 核心庫的定義文件中
2.10.2 DOM 和 BOM 的內(nèi)置對象
DOM 和 BOM 提供的內(nèi)置對象有
Document
、HTMLElement
猫态、Event
佣蓉、NodeList
等。
TypeScript 中會經(jīng)常用到這些類型
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
它們的定義文件同樣在 TypeScript 核心庫的定義文件中
2.10.3 TypeScript 核心庫的定義文件
TypeScript 核心庫的定義文件中定義了所有瀏覽器環(huán)境需要用到的類型亲雪,并且是預(yù)置在 TypeScript 中的
當(dāng)你在使用一些常用的方法的時候勇凭,
TypeScript
實際上已經(jīng)幫你做了很多類型判斷的工作了,比如
Math.pow(10, '2');
// index.ts(1,14): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
上面的例子中义辕,
Math.pow
必須接受兩個number
類型的參數(shù)虾标。事實上Math.pow
的類型定義如下
interface Math {
/**
* Returns the value of a base expression taken to a specified power.
* @param x The base value of the expression.
* @param y The exponent value of the expression.
*/
pow(x: number, y: number): number;
}
再舉一個
DOM
中的例子
document.addEventListener('click', function(e) {
console.log(e.targetCurrent);
});
// index.ts(2,17): error TS2339: Property 'targetCurrent' does not exist on type 'MouseEvent'.
上面的例子中,
addEventListener
方法是在TypeScript
核心庫中定義的
interface Document extends Node, GlobalEventHandlers, NodeSelector, DocumentEvent {
addEventListener(type: string, listener: (ev: MouseEvent) => any, useCapture?: boolean): void;
}
所以
e
被推斷成了MouseEvent
灌砖,而MouseEvent
是沒有targetCurrent
屬性的璧函,所以報錯了
注意,TypeScript 核心庫的定義中不包含 Node.js 部分
2.10.4 用 TypeScript 寫 Node.js
Node.js
不是內(nèi)置對象的一部分基显,如果想用TypeScript
寫Node.js
柳譬,則需要引入第三方聲明文件
npm install @types/node --save-dev
三、進階
3.1 類型別名
類型別名用來給一個類型起個新名字
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver; // 聯(lián)合類型
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
上例中续镇,我們使用 type
創(chuàng)建類型別名。
類型別名常用于聯(lián)合類型
3.2 字符串字面量類型
字符串字面量類型用來約束取值只能是某幾個字符串中的一個
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 沒問題
handleEvent(document.getElementById('world'), 'dbclick'); // 報錯销部,event 不能為 'dbclick'
// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
- 上例中摸航,我們使用
type
定了一個字符串字面量類型EventNames
,它只能取三種字符串中的一種舅桩。
注意酱虎,類型別名與字符串字面量類型都是使用 type 進行定
3.3 元組
- 數(shù)組合并了相同類型的對象,而元組(
Tuple
)合并了不同類型的對象擂涛。 - 元組起源于函數(shù)編程語言,在這些語言中頻繁使用元組读串。
3.3.1 簡單的例子
定義一對值分別為
string
和number
的元組
let user: [string, number] = ['poetries', 22];
當(dāng)賦值或訪問一個已知索引的元素時,會得到正確的類型
let user: [string, number];
user[0] = 'poetries';
user[1] = 22;
user[0].slice(1);
user[1].toFixed(2);
也可以只賦值其中一項
let user: [string, number];
user[0] = 'poetries';
3.3.2 越界的元素
當(dāng)添加越界的元素時,它的類型會被限制為元組中每個類型的聯(lián)合類型
let user: [string, number];
user = ['poetries', 22];
user.push('http://blog.poetries.top');
user.push(true);
// index.ts(4,14): error TS2345: Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
3.4 枚舉
枚舉(
Enum
)類型用于取值被限定在一定范圍內(nèi)的場景恢暖,比如一周只能有七天排监,顏色限定為紅綠藍等
3.4.1 簡單的例子
枚舉使用
enum
關(guān)鍵字來定義:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
枚舉成員會被賦值為從
0
開始遞增的數(shù)字,同時也會對枚舉值到枚舉名進行反向映射
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true
事實上杰捂,上面的例子會被編譯為
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 = {}));
3.4.2 手動賦值
我們也可以給枚舉項手動賦值
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
上面的例子中舆床,未手動賦值的枚舉項會接著上一個枚舉項遞增
如果未手動賦值的枚舉項與手動賦值的重復(fù)了,TypeScript
是不會察覺到這一點的
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true
上面的例子中嫁佳,遞增到
3
的時候與前面的Sun
的取值重復(fù)了挨队,但是TypeScript
并沒有報錯,導(dǎo)致Days[3]
的值先是"Sun"
蒿往,而后又被"Wed"
覆蓋了盛垦。編譯的結(jié)果是
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 = {}));
所以使用的時候需要注意,最好不要出現(xiàn)這種覆蓋的情況瓤漏。
手動賦值的枚舉項可以不是數(shù)字腾夯,此時需要使用類型斷言來讓
tsc
無視類型檢查 (編譯出的js
仍然是可用的):
enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};
var Days;
(function (Days) {
Days[Days["Sun"] = 7] = "Sun";
Days[Days["Mon"] = 8] = "Mon";
Days[Days["Tue"] = 9] = "Tue";
Days[Days["Wed"] = 10] = "Wed";
Days[Days["Thu"] = 11] = "Thu";
Days[Days["Fri"] = 12] = "Fri";
Days[Days["Sat"] = "S"] = "Sat";
})(Days || (Days = {}));
當(dāng)然,手動賦值的枚舉項也可以為小數(shù)或負數(shù)赌蔑,此時后續(xù)未手動賦值的項的遞增步長仍為
1
:
enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1.5); // true
console.log(Days["Tue"] === 2.5); // true
console.log(Days["Sat"] === 6.5); // true
3.4.3 常數(shù)項和計算所得項
枚舉項有兩種類型:常數(shù)項(
constant member
)和計算所得項(computed member
)
前面我們所舉的例子都是常數(shù)項俯在,一個典型的計算所得項的例子:
enum Color {Red, Green, Blue = "blue".length};
上面的例子中,
"blue".length
就是一個計算所得項娃惯。
上面的例子不會報錯跷乐,但是如果緊接在計算所得項后面的是未手動賦值的項,那么它就會因為無法獲得初始值而報錯
enum Color {Red = "red".length, Green, Blue};
// index.ts(1,33): error TS1061: Enum member must have initializer.
// index.ts(1,40): error TS1061: Enum member must have initializer.
3.4.4 常數(shù)枚舉
常數(shù)枚舉是使用
const enum
定義的枚舉類型
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
常數(shù)枚舉與普通枚舉的區(qū)別是趾浅,它會在編譯階段被刪除愕提,并且不能包含計算成員
//上例的編譯結(jié)果是:
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
// 假如包含了計算成員,則會在編譯階段報錯:
const enum Color {Red, Green, Blue = "blue".length};
// index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression.
3.4.5 外部枚舉
外部枚舉(
Ambient Enums
)是使用declare enum
定義的枚舉類型
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
- 之前提到過皿哨,
declare
定義的類型只會用于編譯時的檢查浅侨,編譯結(jié)果中會被刪除。
上例的編譯結(jié)果是:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
- 外部枚舉與聲明語句一樣证膨,常出現(xiàn)在聲明文件中如输。
- 同時使用
declare
和const
也是可以的:
declare const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// 編譯結(jié)果:
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
3.5 類
3.5.1 類的概念
類相關(guān)的概念做一個簡單的介紹
- 類(
Class
):定義了一件事物的抽象特點,包含它的屬性和方法 - 對象(
Object
):類的實例央勒,通過new
生成 - 面向?qū)ο螅?code>OOP)的三大特性:封裝不见、繼承、多態(tài)
- 封裝(
Encapsulation
):將對數(shù)據(jù)的操作細節(jié)隱藏起來崔步,只暴露對外的接口稳吮。外界調(diào)用端不需要(也不可能)知道細節(jié),就能通過對外提供的接口來訪問該對象井濒,同時也保證了外界無法任意更改對象內(nèi)部的數(shù)據(jù) - 繼承(
Inheritance
):子類繼承父類灶似,子類除了擁有父類的所有特性外列林,還有一些更具體的特性 - 多態(tài)(
Polymorphism
):由繼承而產(chǎn)生了相關(guān)的不同的類,對同一個方法可以有不同的響應(yīng)酪惭。比如Cat
和Dog
都繼承自Animal
希痴,但是分別實現(xiàn)了自己的eat
方法。此時針對某一個實例撞蚕,我們無需了解它是Cat
還是Dog
润梯,就可以直接調(diào)用eat
方法,程序會自動判斷出來應(yīng)該如何執(zhí)行eat
- 存取器(
getter & setter
):用以改變屬性的讀取和賦值行為 - 修飾符(
Modifiers
):修飾符是一些關(guān)鍵字甥厦,用于限定成員或類型的性質(zhì)彻采。比如public
表示公有屬性或方法 - 抽象類(
Abstract Class
):抽象類是供其他類繼承的基類枪孩,抽象類不允許被實例化。抽象類中的抽象方法必須在子類中被實現(xiàn) - 接口(
Interfaces
):不同類之間公有的屬性或方法,可以抽象成一個接口醒第。接口可以被類實現(xiàn)(implements
)邻寿。一個類只能繼承自另一個類伯复,但是可以實現(xiàn)多個接口
3.5.2 public private 和 protected
TypeScript
可以使用三種訪問修飾符(Access Modifiers
)搞莺,分別是public
、private
和protected
-
public
修飾的屬性或方法是公有的疚鲤,可以在任何地方被訪問到锥累,默認所有的屬性和方法都是public
的 -
private
修飾的屬性或方法是私有的,不能在聲明它的類的外部訪問 -
protected
修飾的屬性或方法是受保護的集歇,它和private
類似桶略,區(qū)別是它在子類中也是允許被訪問的
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
上面的例子中,
name
被設(shè)置為了public
诲宇,所以直接訪問實例的name
屬性是允許的际歼。
很多時候,我們希望有的屬性是無法直接存取的姑蓝,這時候就可以用 private
了
lass Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
上面的例子編譯后的代碼是:
var Animal = (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
var a = new Animal('Jack');
console.log(a.name);
a.name = 'Tom';
使用
private
修飾的屬性或方法鹅心,在子類中也是不允許訪問的:
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
而如果是用
protected
修飾,則允許在子類中訪問
class Animal {
protected name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
3.5.3 抽象類
abstract
用于定義抽象類和其中的抽象方法纺荧。
什么是抽象類旭愧?
首先,抽象類是不允許被實例化的
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
let a = new Animal('Jack');
// index.ts(9,11): error TS2511: Cannot create an instance of the abstract class 'Animal'.
上面的例子中宙暇,我們定義了一個抽象類
Animal
输枯,并且定義了一個抽象方法sayHi
。在實例化抽象類的時候報錯了客给。
其次,抽象類中的抽象方法必須被子類實現(xiàn)
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');
// index.ts(9,7): error TS2515: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.
上面的例子中肢簿,我們定義了一個類
Cat
繼承了抽象類Animal
靶剑,但是沒有實現(xiàn)抽象方法sayHi
蜻拨,所以編譯報錯了。
下面是一個正確使用抽象類的例子:
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');
上面的例子中桩引,我們實現(xiàn)了抽象方法 sayHi
缎讼,編譯通過了。
需要注意的是坑匠,即使是抽象方法血崭,
TypeScript
的編譯結(jié)果中,仍然會存在這個類厘灼,上面的代碼的編譯結(jié)果是:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Animal = (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
var Cat = (function (_super) {
__extends(Cat, _super);
function Cat() {
_super.apply(this, arguments);
}
Cat.prototype.sayHi = function () {
console.log('Meow, My name is ' + this.name);
};
return Cat;
}(Animal));
var cat = new Cat('Tom');
3.5.4 類的類型
給類加上
TypeScript
的類型很簡單夹纫,與接口類似:
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
3.6 類與接口
3.6.1 類實現(xiàn)接口
實現(xiàn)(
implements
)是面向?qū)ο笾械囊粋€重要概念。一般來講设凹,一個類只能繼承自另一個類舰讹,有時候不同類之間可以有一些共有的特性,這時候就可以把特性提取成接口(interfaces
)闪朱,用implements
關(guān)鍵字來實現(xiàn)月匣。這個特性大大提高了面向?qū)ο蟮撵`活性
舉例來說,門是一個類奋姿,防盜門是門的子類锄开。如果防盜門有一個報警器的功能,我們可以簡單的給防盜門添加一個報警方法称诗。這時候如果有另一個類萍悴,車,也有報警器的功能粪狼,就可以考慮把報警器提取出來退腥,作為一個接口,防盜門和車都去實現(xiàn)它
interface Alarm {
alert();
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
一個類可以實現(xiàn)多個接口
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
上例中再榄,
Car
實現(xiàn)了Alarm
和Light
接口狡刘,既能報警,也能開關(guān)車燈
3.6.2 接口繼承接口
接口與接口之間可以是繼承關(guān)系
interface Alarm {
alert();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}
上例中困鸥,我們使用
extends
使LightableAlarm
繼承Alarm
3.6.3 接口繼承類
接口也可以繼承類:
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
3.6.4 混合類型
可以使用接口的方式來定義一個函數(shù)需要符合的形狀
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
有時候嗅蔬,一個函數(shù)還可以有自己的屬性和方法
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
3.7 泛型
泛型(
Generics
)是指在定義函數(shù)、接口或類的時候疾就,不預(yù)先指定具體的類型澜术,而在使用的時候再指定類型的一種特性
3.7.1 簡單的例子
首先,我們來實現(xiàn)一個函數(shù)
createArray
猬腰,它可以創(chuàng)建一個指定長度的數(shù)組鸟废,同時將每一項都填充一個默認值
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'); // ['x', 'x', 'x']
- 上例中,我們使用了之前提到過的數(shù)組泛型來定義返回值的類型姑荷。
- 這段代碼編譯不會報錯盒延,但是一個顯而易見的缺陷是缩擂,它并沒有準(zhǔn)確的定義返回值的類型:
Array<any>
允許數(shù)組的每一項都為任意類型。但是我們預(yù)期的是添寺,數(shù)組中每一項都應(yīng)該是輸入的value
的類型胯盯。
這時候,泛型就派上用場了:
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'); // ['x', 'x', 'x']
上例中计露,我們在函數(shù)名后添加了
<T>
博脑,其中T
用來指代任意輸入的類型,在后面的輸入value: T
和輸出Array<T>
中即可使用了
接著在調(diào)用的時候票罐,可以指定它具體的類型為 string
叉趣。當(dāng)然,也可以不手動指定胶坠,而讓類型推論自動推算出來
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(3, 'x'); // ['x', 'x', 'x']
3.7.2 多個類型參數(shù)
定義泛型的時候君账,可以一次定義多個類型參數(shù):
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
上例中,我們定義了一個
swap
函數(shù)沈善,用來交換輸入的元組
3.7.3 泛型約束
在函數(shù)內(nèi)部使用泛型變量的時候乡数,由于事先不知道它是哪種類型,所以不能隨意的操作它的屬性或方法
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// index.ts(2,19): error TS2339: Property 'length' does not exist on type 'T'.
上例中闻牡,泛型
T
不一定包含屬性length
净赴,所以編譯的時候報錯了。
這時罩润,我們可以對泛型進行約束玖翅,只允許這個函數(shù)傳入那些包含
length
屬性的變量。這就是泛型約束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
上例中割以,我們使用了
extends
約束了泛型T
必須符合接口Lengthwise
的形狀金度,也就是必須包含length
屬性。
此時如果調(diào)用
loggingIdentity
的時候严沥,傳入的arg
不包含length
猜极,那么在編譯階段就會報錯了
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity(7);
// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
多個類型參數(shù)之間也可以互相約束:
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 });
上例中,我們使用了兩個類型參數(shù)消玄,其中要求
T
繼承U
跟伏,這樣就保證了U
上不會出現(xiàn)T
中不存在的字段
3.7.4 泛型接口
可以使用接口的方式來定義一個函數(shù)需要符合的形狀
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
當(dāng)然也可以使用含有泛型的接口來定義函數(shù)的形狀
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
進一步,我們可以把泛型參數(shù)提前到接口名上
interface CreateArrayFunc<T> {
(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc<any>;
createArray = function<T>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray(3, 'x'); // ['x', 'x', 'x']
注意翩瓜,此時在使用泛型接口的時候受扳,需要定義泛型的類型
3.7.5 泛型類
與泛型接口類似,泛型也可以用于類的類型定義中
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
3.7.6 泛型參數(shù)的默認類型
在
TypeScript 2.3
以后兔跌,我們可以為泛型中的類型參數(shù)指定默認類型勘高。當(dāng)使用泛型時沒有在代碼中直接指定類型參數(shù),從實際值參數(shù)中也無法推測出時,這個默認類型就會起作用
function createArray<T = string>(length: number, value: T): Array<T> {
let result: T[] = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
3.8 聲明合并
如果定義了兩個相同名字的函數(shù)华望、接口或類层亿,那么它們會合并成一個類型
3.8.1 函數(shù)的合并
我們可以使用重載定義多個函數(shù)類型
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('');
}
}
3.8.2 接口的合并
接口中的屬性在合并時會簡單的合并到一個接口中
interface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
相當(dāng)于:
interface Alarm {
price: number;
weight: number;
}
注意,合并的屬性的類型必須是唯一的
interface Alarm {
price: number;
}
interface Alarm {
price: number; // 雖然重復(fù)了立美,但是類型都是 `number`,所以不會報錯
weight: number;
}
interface Alarm {
price: number;
}
interface Alarm {
price: string; // 類型不一致方灾,會報錯
weight: number;
}
// index.ts(5,3): error TS2403: Subsequent variable declarations must have the same type. Variable 'price' must be of type 'number', but here has type 'string'.
接口中方法的合并建蹄,與函數(shù)的合并一樣
interface Alarm {
price: number;
alert(s: string): string;
}
interface Alarm {
weight: number;
alert(s: string, n: number): string;
}
相當(dāng)于:
interface Alarm {
price: number;
weight: number;
alert(s: string): string;
alert(s: string, n: number): string;
}
3.8.3 類的合并
類的合并與接口的合并規(guī)則一致
四、工程
4.1 tsconfig.json
編譯選項
你可以通過
compilerOptions
來定制你的編譯選項
{
"compilerOptions": {
/* 基本選項 */
"target": "es5", // 指定 ECMAScript 目標(biāo)版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模塊: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在編譯中的庫文件
"allowJs": true, // 允許編譯 javascript 文件
"checkJs": true, // 報告 javascript 文件中的錯誤
"jsx": "preserve", // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相應(yīng)的 '.d.ts' 文件
"sourceMap": true, // 生成相應(yīng)的 '.map' 文件
"outFile": "./", // 將輸出文件合并為一個文件
"outDir": "./", // 指定輸出目錄
"rootDir": "./", // 用來控制輸出目錄結(jié)構(gòu) --outDir.
"removeComments": true, // 刪除編譯后的所有的注釋
"noEmit": true, // 不生成輸出文件
"importHelpers": true, // 從 tslib 導(dǎo)入輔助工具函數(shù)
"isolatedModules": true, // 將每個文件做為單獨的模塊 (與 'ts.transpileModule' 類似).
/* 嚴(yán)格的類型檢查選項 */
"strict": true, // 啟用所有嚴(yán)格類型檢查選項
"noImplicitAny": true, // 在表達式和聲明上有隱含的 any類型時報錯
"strictNullChecks": true, // 啟用嚴(yán)格的 null 檢查
"noImplicitThis": true, // 當(dāng) this 表達式值為 any 類型的時候裕偿,生成一個錯誤
"alwaysStrict": true, // 以嚴(yán)格模式檢查每個模塊洞慎,并在每個文件里加入 'use strict'
/* 額外的檢查 */
"noUnusedLocals": true, // 有未使用的變量時,拋出錯誤
"noUnusedParameters": true, // 有未使用的參數(shù)時嘿棘,拋出錯誤
"noImplicitReturns": true, // 并不是所有函數(shù)里的代碼都有返回值時劲腿,拋出錯誤
"noFallthroughCasesInSwitch": true, // 報告 switch 語句的 fallthrough 錯誤。(即鸟妙,不允許 switch 的 case 語句貫穿)
/* 模塊解析選項 */
"moduleResolution": "node", // 選擇模塊解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相對模塊名稱的基目錄
"paths": {}, // 模塊名到基于 baseUrl 的路徑映射的列表
"rootDirs": [], // 根文件夾列表焦人,其組合內(nèi)容表示項目運行時的結(jié)構(gòu)內(nèi)容
"typeRoots": [], // 包含類型聲明的文件列表
"types": [], // 需要包含的類型聲明文件名列表
"allowSyntheticDefaultImports": true, // 允許從沒有設(shè)置默認導(dǎo)出的模塊中默認導(dǎo)入。
/* Source Map Options */
"sourceRoot": "./", // 指定調(diào)試器應(yīng)該找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定調(diào)試器應(yīng)該找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成單個 soucemaps 文件重父,而不是將 sourcemaps 生成不同的文件
"inlineSources": true, // 將代碼與 sourcemaps 生成到一個文件中花椭,要求同時設(shè)置了 --inlineSourceMap 或 --sourceMap 屬性
/* 其他選項 */
"experimentalDecorators": true, // 啟用裝飾器
"emitDecoratorMetadata": true // 為裝飾器提供元數(shù)據(jù)的支持
}
}
4.2 TypeScript 編譯
運行
tsc -p ./path-to-project-directory
。tsc -w
來啟用TypeScript
編譯器的觀測模式房午,在檢測到文件改動之后矿辽,它將重新編譯
指定需要編譯的文件
{
"files": [
"./some/file.ts"
]
}
使用 include 和 exclude 選項來指定需要包含的文件,和排除的文件
{
"include": [
"./folder"
],
"exclude": [
"./folder/**/*.spec.ts",
"./folder/someSubFolder"
]
}
五郭厌、一些例子演示
5.1 定義ajax請求數(shù)據(jù)接口
interface Config{
type:string;
url:string;
data?:string;
dataType:string;
}
//原生js封裝的ajax
function ajax(config:Config){
var xhr=new XMLHttpRequest();
xhr.open(config.type,config.url,true);
xhr.send(config.data);
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
console.log('chengong');
if(config.dataType=='json'){
console.log(JSON.parse(xhr.responseText));
}else{
console.log(xhr.responseText)
}
}
}
}
ajax({
type:'get',
data:'name=zhangsan',
url:'http://a.itying.com/api/productlist', //api
dataType:'json'
})
5.2 函數(shù)類型接口-對方法約束
// 函數(shù)類型接口:對方法傳入的參數(shù) 以及返回值進行約束 批量約束
// 加密的函數(shù)類型接口
interface encrypt{
(key:string,value:string):string;
}
var md5:encrypt=function(key:string,value:string):string{
//模擬操作
return key+value;
}
console.log(md5('name','zhangsan'));
var sha1:encrypt=function(key:string,value:string):string{
//模擬操作
return key+'----'+value;
}
console.log(sha1('name','lisi'));
5.3 可索引接口:數(shù)組袋倔、對象的約束(不常用)
5.3.1 可索引接口-對數(shù)組的約束
interface UserArr{
[index:number]:string
}
var arr:UserArr=['aaa','bbb'];
console.log(arr[0]);
5.3.2 可索引接口-對對象的約束
interface UserObj{
[index:string]:string
}
var arr:UserObj={name:'張三'};
5.3.3 類類型接口:對類的約束
- 抽象類抽象有點相似
interface Animal{
name:string;
eat(str:string):void;
}
class Dog implements Animal{
name:string;
constructor(name:string){
this.name=name;
}
eat(){
console.log(this.name+'吃糧食')
}
}
var d=new Dog('小黑');
d.eat();
class Cat implements Animal{
name:string;
constructor(name:string){
this.name=name;
}
eat(food:string){
console.log(this.name+'吃'+food);
}
}
var c=new Cat('小花');
c.eat('老鼠');
5.4 接口的擴展
接口繼承接口 類實現(xiàn)接口
interface Animal{
eat():void;
}
interface Person extends Animal{
work():void;
}
class Programmer{
public name:string;
constructor(name:string){
this.name=name;
}
coding(code:string){
console.log(this.name+code)
}
}
class Web extends Programmer implements Person{
constructor(name:string){
super(name)
}
eat(){
console.log(this.name+'喜歡吃饅頭')
}
work(){
console.log(this.name+'寫代碼');
}
}
var w=new Web('小李');
// w.eat();
w.coding('寫ts代碼');
5.5 泛型類接口
5.5.1 泛型類 泛型方法
- 泛型:軟件工程中,我們不僅要創(chuàng)建一致的定義良好的
API
折柠,同時也要考慮可重用性宾娜。 組件不僅能夠支持當(dāng)前的數(shù)據(jù)類型,同時也能支持未來的數(shù)據(jù)類型液走,這在創(chuàng)建大型系統(tǒng)時為你提供了十分靈活的功能碳默。 - 在像
C#
和Java
這樣的語言中,可以使用泛型來創(chuàng)建可重用的組件缘眶,一個組件可以支持多種類型的數(shù)據(jù)嘱根。 這樣用戶就可以以自己的數(shù)據(jù)類型來使用組件。 - 通俗理解:泛型就是解決類接口方法的復(fù)用性巷懈、以及對不特定數(shù)據(jù)類型的支持(類型校驗)
// 只能返回string類型的數(shù)據(jù)
function getData(value:string):string{
return value;
}
// 同時返回 string類型 和number類型 (代碼冗余)
function getData1(value:string):string{
return value;
}
function getData2(value:number):number{
return value;
}
//同時返回 string類型 和number類型 any可以解決這個問題
function getData(value:any):any{
return '哈哈哈';
}
getData(123);
getData('str');
//any放棄了類型檢查,傳入什么 返回什么该抒。比如:傳入number 類型必須返回number類型 傳入 string類型必須返回string類型
//傳入的參數(shù)類型和返回的參數(shù)類型可以不一致
function getData(value:any):any{
return '哈哈哈';
}
T
表示泛型,具體什么類型是調(diào)用這個方法的時候決定的
// T表示泛型顶燕,具體什么類型是調(diào)用這個方法的時候決定的
function getData<T>(value:T):T{
return value;
}
getData<number>(123);
getData<string>('1214231');
getData<number>('2112'); /*錯誤的寫法*/
function getData<T>(value:T):any{
return '2145214214';
}
getData<number>(123); //參數(shù)必須是number
getData<string>('這是一個泛型');
泛型類
泛型類:比如有個最小堆算法凑保,需要同時支持返回數(shù)字和字符串
a - z
兩種類型冈爹。 通過類的泛型來實現(xiàn)
// 基本寫法 但是不能傳入字符串
class MinClass{
public list:number[]=[];
add(num:number){
this.list.push(num)
}
min():number{
var minNum=this.list[0];
for(var i=0;i<this.list.length;i++){
if(minNum>this.list[i]){
minNum=this.list[i];
}
}
return minNum;
}
}
var m=new MinClass();
m.add(3);
m.add(22);
m.add(23);
m.add(6);
m.add(7);
alert(m.min());
類的泛型
// 通過泛型改寫 可以同時傳入number 字符串等
//類的泛型
class MinClas<T>{
public list:T[]=[];
add(value:T):void{
this.list.push(value);
}
min():T{
var minNum=this.list[0];
for(var i=0;i<this.list.length;i++){
if(minNum>this.list[i]){
minNum=this.list[i];
}
}
return minNum;
}
}
var m1=new MinClas<number>(); /*實例化類 并且制定了類的T代表的類型是number*/
m1.add(11);
m1.add(3);
m1.add(2);
alert(m1.min())
var m2=new MinClas<string>(); /*實例化類 并且制定了類的T代表的類型是string*/
m2.add('c');
m2.add('a');
m2.add('v');
alert(m2.min())
5.5.2 泛型接口
1. 方式1
interface ConfigFn{
<T>(value:T):T;
}
var getData:ConfigFn=function<T>(value:T):T{
return value;
}
getData<string>('張三');
// getData<string>(1243); //錯誤
2. 方式2
interface ConfigFn<T>{
(value:T):T;
}
function getData<T>(value:T):T{
return value;
}
var myGetData:ConfigFn<string>=getData;
myGetData('20'); /*正確*/
// myGetData(20) //錯誤