那么我們開始吧,
我們從基礎語言類型開始
JavaScript 的類型分為兩種:原始數據類型(Primitive data types)
和對象類型(Object types)
。
原始數據類型包括:布爾值、數值、字符串、null、undefined
以及 ES6 中的新類型 Symbol。
本節主要介紹前五種原始數據類型在 TypeScript
中的應用。
原始數據類型
布爾值
TypeScript
是用:
來進行類型申請的。
let isDone: boolean = false;
// 編譯通過
// 后面約定,未強調編譯錯誤的代碼片段,默認為編譯通過
注意,使用構造函數 Boolean
創造的對象不是布爾值:
let createdByNewBoolean: boolean = new Boolean(1);
// index.ts(1,5): error TS2322: Type 'Boolean' is not assignable to type 'boolean'.
// 后面約定,注釋中標出了編譯報錯的代碼片段,表示編譯未通過
事實上 new Boolean()
返回的是一個Boolean
對象:
let createdByNewBoolean: Boolean = new Boolean(1);
直接調用 Boolean
也可以返回一個 boolean
類型:
let createdByBoolean: boolean = Boolean(1);
在 TypeScript 中,boolean 是 JavaScript 中的基本類型,而 Boolean 是 JavaScript 中的構造函數。其他基本類型(除了 null 和 undefined)一樣,不再贅述。
下面的類型類似:
數值
let age: number = 23;
字符串
let myName:string = 'Tom';
編譯結果為;
var isDone = false;
var age = 23;
var myName = 'Tom';
空值
在 JavaScript
沒有空值(Void
)的概念,在 TypeScript
中,可以用 void
表示沒有任何返回值的函數:
function alertName(): void {
alert("my name is Tom")
};
聲明一個 void
類型的變量沒有什么用,因為你只能將它賦值為 undefined
和 null
:
let unusable: void = undefined;
Null和Undefined
這兩個原始數據類型比較有意思,他們的類型變量只能賦值為他們本身,undefined
為undefined
,null
只能為null
.
let u: undefined = undefined;
let n: null = null;
不過,它和void
的區別是,他們兩個是所有類型的子類型。也就是說undefined
和null
類型的變量,可以賦值給number
類型變量:
// 這樣不會報錯
let num: number = undefined;
// 這樣也不會報錯
let u: undefined;
let num: number = u;
而 void
類型的變量不能賦值給 number
類型的變量:
任意值
任意值(Any)用來表示允許賦值為任意類型
那么什么是任意值類型呢?
如果是一個普通類型,在賦值過程中改變類型是不被允許的:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
編譯報錯:
hello.ts:25:1 - error TS2322: Type '7' is not assignable to type 'string'.
25 myFavoriteNumber = 7;
~~~~~~~~~~~~~~~~
但是any
類型可以,允許被賦值為任意類型。
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
任意值的屬性和方法
在任意值上訪問任何屬性都是允許的:
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
也允許調用任何方法:
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
可以這樣認為,聲明一個變量為任意值后,對它的任何操作,返回的內容的類型都是任意值。
未聲明類型的變量
如果變量在聲明的時候沒有指定類型,那么它的默認類型為任意值類型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
相當于
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
類型推論
如果沒有明確的指定類型,那么TypeScript
會依照類型推論Type Inference
的規則推斷出一個類型。
什么是類型推論呢?
例如:
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'.
報的錯誤都一樣,就是說,在你聲明變量的時候,如果你沒有設置類型,但是你給它指定了具體的值,那么它就會推斷它的類型為你制定值的類型。
同理,如果你在聲明變量的時候沒有指定值,那么它就推斷它的類型為any
任意值類型。
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
聯合類型
聯合類型(Union Types
)表示取值可以為多種類型中的一種。
let myFavoriteNumber: string | number = 'seven';
myFavoriteNumber = 7;
同樣是上面的代碼,你設置了字符串和數字,那么它就可以設置這兩種。
let myFavoriteNumber: string | number ;
myFavoriteNumber = false;
// index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
// Type 'boolean' is not assignable to type 'number'.
聯合類型是用豎線 |
來分割每個類型。
這里的含義是,你可以是字符串或者數字,但是不可以是其他的類型。
訪問聯合類型的屬性或方法
當TypeScript
不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型里共有的屬性或方法:
function getLength(something: string | number): number {
return something.length;
}
hello.ts:39:22 - error TS2339: Property 'length' does not exist on type 'string
上例中,length
不是string
和number
的共有屬性,所以會報錯。
訪問string
和number
的共有屬性是沒有問題的:
function getString(something: string | number): string {
return something.toString();
}
聯合類型的變量在賦值的時候,會根據類型推論的規則推斷出一個類型:
let myFavoriteNumber: string|number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length);
myFavoriteNumber = 7;
console.log(myFavoriteNumber.lenght);
// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
上面的第二行,類型推斷字符串,所以length
不報錯,
而第四行,類型推斷為數字,所以length
為不報錯。
對象的類型——接口
這個在java
語言中非常重要的概念,有一個說法是面向接口編程
。在TypeScript
中,我們使用接口interface
來定義對象的類型。
什么是接口
在面向對象語言中,它是對行為的抽象,而具體如何行動需要由類classes
去實現implements
.
而TypeScript
中的接口是一個非常靈活的概念,除了可用于一部分行為進行抽象外,也常用于對象的形狀Shape
進行概括。
例如:
interface Person {
name:string;
age:number;
}
let tom:Person = {
name:"Tom",
age:23
};
這個有點想抽象類,我們看到上面代碼中,我們創建了一個對象tom
,然后把它的類型設置為接口Person
。這樣的話,我們就約束了tom
的形狀必須和接口Person
一直。接口一般首字母大寫。有的編程語言中會建議接口的名稱加上I
為前綴。
因為你設定了對象的類型是接口Person
,所以必須完全一致,少一些屬性和多一些屬性,通通不允許:
interface Person {
name:string;
age:number;
}
let tom:Person = {
name:"Tom",
};
//hello.ts:59: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:23,
gender:'male'
};
//hello.ts:62: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'.
所以,賦值的時候,變量的形狀必須和接口的形狀保持一致。
可是我不想保持一致怎么辦呢?可選屬性出場
可選屬性
可選屬性是通過變量后面加一個?
問號來實現的,它的意思是你可以選擇這個屬性不存在。但是不可以添加未定義的屬性。
interface Person {
name:string;
age?:number;
}
let tom:Person = {
name:"Tom",
};
可選屬性,你可以存在或者不存在
interface Person {
name:string;
age?:number;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
//hello.ts:62: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'.
不可以添加不存在的屬性。
對象的類型 接口的意義就是為了約束對象的創建。
任意類型
有時候我們希望一個借口允許有任意的屬性,可以使用下面的方式:
interface Person {
name:string;
age?:number;
[propName: string]:any;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
我們通過[propName: string]
定義了任意屬性取string
的值,而我們需要注意的是,一旦定義了任意屬性,那么確定屬性和可選屬性都必須是它的子屬性。
interface Person {
name:string;
age?:number;
[propName: string]:string;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
我們把上面的any
改為string
,那么不確定屬性age
為number
,不屬于string
,就報錯了。
編譯的結果:
hello.ts:56:5 - error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
56 age?:number;
~~~
hello.ts:60:5 - error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
Property 'age' is incompatible with index signature.
Type 'number' is not assignable to type 'string'.
60 let tom:Person = {
只讀屬性
我們還可以設置只讀類型,只有在創建的時候賦值,其他的時候不可以賦值,那么就可以使用readonly
定義只讀屬性了。
interface Person {
readonly id:number;
name:string;
age?:number;
[propName: string]:any;
}
let tom:Person = {
id:12345,
name:"Tom",
age:23,
gender:'male'
};
tom.id = 2222;
//hello.ts:68:5 - error TS2540: Cannot assign to 'id' because //it is a constant or a read-only property.
//68 tom.id = 2222;
類似于上面的代碼,我們把readonly
放在屬性名稱的前面,在上面的tom
對象中,我們創建的時候就已經直接賦值了,所以在后面再次賦值的時候,就報錯。
注意:只讀的約束存在于第一次給對象賦值的時候,而不是第一次給只讀屬性賦值的時候。
interface Person {
readonly id:number;
name:string;
age?:number;
[propName: string]:any;
}
let tom:Person = {
name:"Tom",
age:23,
gender:'male'
};
tom.id = 2222;
hello.ts:61:5 - error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
Property 'id' is missing in type '{ name: string; age: number; gender: string; }'.
61 let tom:Person = {
~~~
hello.ts:68:5 - error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
68 tom.id = 2222;
上面報錯有兩次,一是在創建對象的時候,沒有給只讀屬性id
賦值,二是它是只讀屬性,不可以在此賦值。
關于接口行為的約束,我們在后面會有詳細的章節。
數組的類型
在TypeScript
中,數組類型有多重定義方式,比較靈活。
「類型 + 方括號」表示法
和java
的類似
let array:number[] = [1,2,3,4,5,6];
let sty_arr:string[] = ['1','2'];
數組中不可以出現其他的數據類型:
let array:number[] = [1,2,3,4,5,6,"1"];
hello.ts:70:35 - error TS2322: Type 'string' is not assignable to type 'number'.
70 let array:number[] = [1,2,3,4,5,6,"1"];
我們試一下是否可以聲明聯合類型的數組,既包含字符串又包含數字
let com_arr: string | number [] = ["1",1];
hello.ts:73:5 - error TS2322: Type '(string | number)[]' is not assignable to type 'string | number[]'.
Type '(string | number)[]' is not assignable to type 'number[]'.
Type 'string | number' is not assignable to type 'number'.
Type 'string' is not assignable to type 'number'.
73 let com_arr: string | number [] = ["1",1];
看上面錯誤,這樣聲明的數組是不行的。需要改成下面這樣。
let com_arr: (string | number) [] = ["1",1];
果然可以。666,不過我們還是不要這樣用了,你用TypeScript
就是為了約束,你這樣玩還不如直接寫:
let com_arr = ["1", 1];
數組的泛型
我們也可以用數組的泛型來展示數組。Array<elemType>
let array: Array<number> = [1,2,3,4,5];
let array1: Array<string> = ["1","2"];
let array3: Array<string|number> = ["1","2",1];
let array4:Array<any> = [1,"2",{"a":1}];
同樣我們可以用any
來定義數組。
關于泛型,可以參考泛型一章.
用接口表示數組
interface NumberArray {
[index:number]:number;
}
// let array:NumberArray = [1,2,3,4];
let array :NumberArray = ["a"];
hello.ts:86:27 - error TS2322: Type 'string' is not assignable to type 'number'.
86 let array :NumberArray = ["a"];
hello.ts:82:5
82 [index:number]:number;
~~~~~~~~~~~~~~~~~~~~~~
The expected type comes from this index signature.
NumberArray
表示:只要index
的類型是number
,那么值必須是number
如果我們設置的數組是字符串的話,那么它會報錯。
類數組
類數組不是數組類型,比如內置對象arguments:
function sum() {
let args:number [] = arguments;
}
hello.ts:89:9 - error TS2322: Type 'IArguments' is not assignable to type 'number[]'.
Property 'pop' is missing in type 'IArguments'.
89 let args:number [] = arguments;
我們可以看到上面的錯誤說的是IArguments
類型無法匹配為nujber []
類型。所以我們可以這樣寫:
function sum() {
let args:IArguments = arguments;
}
沒有報錯,妥妥的。
函數的類型
大家要記得,函數是JavaScript的一等公民
函數聲明
在JavaScript
中,有兩種常見的定義函數的方式--函數聲明和函數表達式:
//函數聲明 (Function Declaration)
function sum(x,y){
return x+y;
}
//函數表達式 (Function Expression)
let mySum = function(x,y){
return x + y;
}
一個函數有輸入和輸出,要在 TypeScript
中對其進行約束,需要把輸入和輸出都考慮到,其中函數聲明的類型定義較簡單:
function sum(x:number,y:number):number {
return x + y;
}
注意:輸入多余的或者少輸入都會報錯。我就不在這里試了。
函數表達式
感覺這里是個坑。
聲明一個函數表達式,會寫成這樣:
let mySum = function (x: number, y: number):number {
return x + y;
}
沒有報錯,可以編譯,實際上上面的代碼只對燈油右側的匿名函數進行了類型定義,而等號左邊的mySum
,是對賦值操作進行類型推論出來的。如果我們需要手動為它添加類型,則應該寫成這樣。
let mySum:(x:number,y:number)=>number = function (x: number, y: number):number {
return x + y;
}
這里不要把TypeScript
的=>
和ES6
的=>
混淆了。
怎么可能不混淆嗎?
在TypeScript
的類型定義中,=>
用來表示函數的定義,左邊是輸入的參數的類型,右邊是返回的類型。
在ES6
中,=>
表示箭頭函數。不在介紹。
用接口定義函數的形狀
那么我們現在用接口來定義個函數所需要的形狀:
interface SearchFunc {
(source:string,subString:string):boolean;
}
let mySearch:SearchFunc = function (source:string,subString:string) {
return source.search(subString) !== -1;
}
我們用接口定義了函數的形狀,左邊是輸入的類型,右邊是輸出的類型。
可選參數
前面我們說道輸入多余或者少輸入都會報錯,那么如何定義和選擇的參數呢?與接口中的可選屬性類型,我們用?
表示可選的參數:
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
需要注意的是,可選參數必須接在必需參數后面。換句話說,可選參數后面不允許再出現必須參數了:
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.
參數默認值
TypeScript
中參數的默認值和ES6
一樣的寫法,TypeScript
會將添加了默認值的參數識別為可選參數:
function buildName(firstName:string,lastName:string = "Cat") {
return firstName +" " +lastName;
}
let tomcat = buildName('Tom','Cat');
let tom = buildName('Tom');
此時就不受可選參數必須接在必須參數后面
的限制了:
function buildName(firstName:string,lastName:string = "Cat") {
return firstName +" " +lastName;
}
let tomcat = buildName('Tom','Cat');
let tom = buildName(undefined,'Tom');
剩余參數
ES6
中,可以使用 ...rest
的方式獲取函數中的剩余參數(rest
參數):
function push(array,...items) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a,2,3,4);
我們可以看到,實際上items
是一個數組,所以我們可以用數組的方式來定義它。
function push(array:any [],...items:any []) {
items.forEach(function (item) {
array.push(item);
});
}
let a = [];
push(a,2,3,4);
需要注意的是,rest
參數只能是最后一個參數。
重載
重載允許一個函數接受不同數量或者不同類型的參數時,做出不同的處理。
比如我們實現下面的函數。利用聯合類型,我們可以這樣實現。
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
的函數類型:
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
會優先從最前面的函數定義開始匹配,所以多個函數定義如果有包含關系,需要優先把精確的電影以寫在前面。
類型斷言
類型斷言(Type Assertion) 可以用來手動指定一個值的類型。但是它并不是類型轉換。
語法
<類型>值
或
值 as 類型
在tsx
語法中必須用后一種。
例如:將一個聯合類型的變量指定為更加具體的的類型。
之前提到過,當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型里共有的屬性或方法:
function getLength(something:string|number):number {
return something.length;
}
hello.ts:154:22 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
154 return something.length;
我們有時候需要在還不確定類型的時候就訪問其中的一個類型的屬性或方法,比如:
function getLength(something:string|number):number {
if(something.length){
return something.length;
}else{
return something.toString.length;
}
}
hello.ts:154:17 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
154 if(something.length){
~~~~~~
hello.ts:155:25 - error TS2339: Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
155 return something.length;
可以看到上面的報錯,這個時候我們就可以使用類型斷言,將something
斷言成string
:
function getLength(something:string|number):number {
if((<string>something).length){
return (<string>something).length;
}else{
return something.toString.length;
}
}
類型斷言的用法如上,在需要斷言的變量前加上<Type>
就可以。
類型斷言不是類型轉換,斷言成一個聯合類型中不存在的類型是不允許的。
function boBoolean(something:string|number):boolean {
return <boolean>something;
}
hello.ts:162:12 - error TS2352: Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'number' is not comparable to type 'boolean'.
162 return <boolean>something;
類型斷言不是類型轉換,因為你不能把它強制轉換為不存在的類型。
聲明文件
內置對象
JavaScript
中有很多內置對象,它們可以直接在 TypeScript
中當做定義好了的類型。
內置對象是指根據標準在全局作用域(Global)
上存在的對象。這里的標準是指 ECMAScript 和其他環境(比如 DOM)的標準。
ECMAScript 的內置對象
ECMAScript
標準提供的內置對象有:
Boolean
,String
,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]/;
let s:String = new String("a");
let n:Number = new Number(1);
更多的內置對象,可以查看MDN的文檔
而他們的定義文件,則在 TypeScript 核心庫的定義文件中。
DOM 和 BOM 的內置對象
DOM和BOM提供的內置對象:
Document、HTMLElement、Event、NodeList
等。
TypeScript
中經常用到這些類型:
let body:HTMLElement = document.body;
let allDiv:NodeList = document.querySelectorAll('div');
document.addEventListener('click',function (e:MouseEvent) {
//DO something
})
它們同樣定義在 TypeScript 核心庫的定義文件中。
大家加油。。。