基礎
- 原始數據類型
- 任意值
- 類型推論
- 聯合類型
- 接口
- 數組
- 函數
- 類型斷言
- 申明文件
- 內置對象
原始數據類型
Javascript 的類型分為兩種: 原始數據類型(Primitive data types)和對象類型(Object types)。
原始數據類型包括: 布爾值、數字、字符串、null、undefined、以及ES6中的新類型 Symbol。
布爾值
布爾類型,使用 boolean 定義
let isDone: boolean = false
// or
let createByBoolean: boolean = Boolean(1)
布爾對象, 使用Boolean 構造函數
let createByNewBoolean: Boolean = new Boolean(1)
數字
數字類型, 使用 number 定義
let digital: number = 6
let notANumber: number = NaN
let infinityNumber: number = Infinity
數字對象, 使用Number構造函數
let six: Number = new Number(6)
字符串
字符串類型,使用string定義:
let myName: String = 'Tao' // 這里不用 name 是因為 在ts name 已經被聲明過,這里不能使用
// 模板字符串
let sentence: string = `Hello, my name is ${myName}`
字符串對象, 使用String定義
let myName: String = new String('Tao')
console.log(myName)
// 模板字符串
let sentence: String = new String(`Hello, my name is ${myName}`) ;
console.log(sentence)
空值
JavaScript 沒有空值(Void)的概念,在 TypeScirpt 中,可以用 void 表示沒有任何返回值的函數:
function alertName(): void {
alert('My name is Tao')
}
聲明一個 void 類型的變量沒有什么用,因為你只能將它賦值為 undefined 和 null:
let unusable: void = undefined
Null 和 Undefined
在 TypeScript 中,可以使用 null 和 undefined 來定義這兩個原始數據類型:
let u: undefined = undefined
let n: null = null
undefined類型的變量只能被賦值為 undefined,null 類型的變量只能被賦值為 null。
與 void 的區別是,undefined 和 null 是所有類型的子類型。也就是說 undefined 類型的變量,可以賦值給 number 類型的變量,而 void 類型的變量不能賦值給 number 類型的變量:
let num: number = undefined
let str: string = null
// 這些都是允許的, 不會報錯
任意值
- 如果是一個普通類型,在復制過程中改變類型是不背允許的,但如果是 any 類型,則雲訊被賦值為任意類型
- 任意值(Any) 用來表示允許賦值為任意類型
- 在任意值上訪問任何屬性都是允許的,也允許調用任何方法
- 聲明一個變量為任意值之后面對它的任何操作,返回的內容的類型都是任意類型
- 變量如果在聲明的時候,未指定其類型,那么它會被識別為任意值類型
let myFavoriteNumber: any = 'six'
myFavoriteNumber = 7
let anyThing: any = 'hello'
console.log(anyThing.myName)
anyThing.setName('Tao').sayHello()
類型推論
如果沒有明確的指定類型, 那么 Typescript 會依照類型推論(Type Inference)的規則推斷出一個類型
Typescript 2.1 中,編譯器會考慮對 myFavoriteNumber 的最后一次賦值來檢查類型。
let myFavoriteNumber = 'six'
// 等價於 let myFavoriteNumber: string = 'six'
聯合類型
- 聯合類型(Union Types)表示取值可以為多種類型中的一種。
- 聯合類型使用 | 分隔每個類型
let myFavoriteNumber: string | number
myFavoriteNumber = 'six'
myFavoriteNumber = 6
這里的 string | number 的含義是, 允許 myFavoriteNumber 的類型是 string 或者 number, 但是不能是其他類型。
當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型里共有的屬性或方法。
function getString( someThing: string |number ): string {
// 這里的 函數 getString 中的第二個 : string 代表返回值類型
return someThing.toString()
}
聯合類型的變量在被賦值的時候,會根據類型推論的規則推斷出一個類型
接口
Typescript 中的接口是一個非常靈活的概念, 除了可以用於對類的一部分行為進行抽象以外, 也常用於對 「 對象的形狀(Shape) 」進行描述。
interface Person {
name: sting,
age: number
}
使用接口類型賦值時, 變量的形狀必須和接口保持一致,屬性數量和類型保持一致,不允許添加未定義的屬性。
可選屬性,有時候我們希望不要完全匹配一個形狀,那么可以用可選屬性。 使用 ?
interface Person {
name: string,
age?: number,
}
let tao: Person = {
name: 'Tao'
}
任意屬性,有時候我們希望一個借口允許有任意屬性
interface Person {
name: string,
age?: number,
[propName: string]: any, // 使用任意屬性后, 將不會對對象的任意屬性長度進行限制
}
let tao: Person = {
name: 'Tao',
sex: 'male'
}
注意:一旦定義了任意屬性,那么確定屬性和可選屬性都必須是它的子屬性
interface Person {
name: string,
age?: number,
[porpName: string]: string
}
let tao: Person = {
name: 'Tao',
age: 26, // err 25 是number類型, 不是string的子屬性
sex: 'male'
}
// 所以通常情況下我們會將 任意屬性定位 any(任意值)
只讀屬性,對象中的一些字段只能在創建的時候被賦值,用 readonly 定義只讀屬性。
注意: 只讀約束存在於第一次給對象賦值的時候, 而不是第一個給只讀屬性賦值的時候
interface Person {
readonly id: number
}
// 第一次給對象賦值, 激活只讀約束
let tao: Person = {
id: 89757, // success
}
tao.id = 9527 // err 這里會拋出異常, 因為此處已經不屬於第一個個對象賦值
數組
定義數組有多種定義方式
-
「 類型 + 方括號 」表示法
let fibonacci: number[] = [1, 1, 2, 3, 5] // 這里的數組 不允許出現其他類型 fibonacci.push('8') // err let fibonacci: number[] = [1, '1', 2, 3, 5] //err
-
使用數組泛型(Generic)Array 來表示數組
let fibonacci: Array<number> = [1, 1, 2, 3, 5]
-
用接口表示數組
interface NumberArray { [index: number]: number } let fibonacci: NumberArray = [1, 1, 2, 3, 5]
-
any表示數組中允許出現任意類型
let list: any[] = ['tao', 26, { sex: 'male' }]
-
類數組(Array-like Object)不是數組類型, 比如
arguments
:事實上常見的類數組都有自己的接口定義,如
IArguments
,NodeList
,HTMLCollection
等:function sum() { let args: IArguments = arguments }
函數
函數是 javascript 中的一等公民
函數的定義方式有兩種-- 函數申明(Function Declaration)和函數表達式(Function Expression):
-
函數申明(Function Declaration)
function sum(x: number, y: number): number { return x + y }
注意,輸入多余的(或者少於要求的)參數,是不被允許的:
-
函數表達式(Function Expression)
let mySum = function(x: numnber, y: number): number { return x + y }
-
用接口定義函數的形狀
我們也可以使用接口的方式來定義一個函數需要符合的形狀
interface SearchFunc { (source: string, subString: string): boolean } let mySearch: SearchFunc mySearch = function(source: string, subString: string): boolean { return source.search(subString) !=== -1 }
-
可選參數
之前提到, 輸入多余的(或者少於要求的)參數是不被允許的, 那么如何定義可選的參數呢?
這里與接口中的可選屬性類似 , 也是使用
?
表示可選參數function buildName(firstName: string, lastName?: string): string { if (lastName) { return firstName + ' ' + lastName; } else { return firstName; } } 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('Tom');
此時就不受「可選參數必須接在必需參數后面」的限制了
-
剩余參數(rest)
function push( array: any[], ...items: any[] ) { items.forEach( function(item) { array.push(item) } ) }
rest 參數只能是最后一個參數
-
重載
重載允許一個函數接受不同數量或類型的參數時, 作出不同的處理。
比如,我們需要實現一個函數
reverse
,輸入數字123
的時候,輸出反轉的數字321
,輸入字符串'hello'
的時候,輸出反轉的字符串'olleh'
。利用聯合類型,我們可以這么實現:
function reverse(x: number | string): number | string { if(typeof x === 'number') { return Number( x.toString().split('').reverse().join('') ) } else { 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 { return x.split('').reverse().join('') ) } }
上例中,我們重復定義了多次函數
reverse
,前幾次都是函數定義,最后一次是函數實現。在編輯器的代碼提示中,可以正確的看到前兩個提示。注意,TypeScript 會優先從最前面的函數定義開始匹配,所以多個函數定義如果有包含關系,需要優先把精確的定義寫在前面。
類型斷言
類型斷言(Type Assertion)可以用來手動指定一個值的類型。
語法
<類型>值
// or
值 as 類型
在 tsx 語法(React 的 jsx 語法的 ts 版)中必須用后一種。
將一個聯合類型的變量指定為一個更加具體的類型
上面說過,當 TypeScript 不確定一個聯合類型的變量到底是哪個類型的時候,我們只能訪問此聯合類型的所有類型里共有的屬性或方法:
function getLength(something: string | number): number {
return something.length
}
// 報錯 number 中無法訪問到 length 這個屬性值
而有的時候,我們確實要在還不確定類型的時候就訪問其中一個類型的屬性或方法,此時我們可以使用類型斷言
function getLength(something: string | number): number {
if( (<string>something).length ) {
return (<string>something).length
} else {
return something.toString().lengt
}
}
類型斷言不是類型轉換,斷言成一個聯合類型中不存在的類型是不允許的:
function getBoolean(something: string | number): boolean {
return <boolean>something
}
申明文件
當時用第三方庫時,我們需要引用它的聲明文件。
申明語句:使用declare關鍵字來定義類型
如:我們需要使用第三方庫Jquery獲取一個id是foo的元素, 但在 ts 中, 編譯器並不知道 $
或者 jQuery
是什么東西。此時,我們需要使用 declare var
來定義它的類型
declare var jQuery: ( selector: string ) => any
jQuery('#foo')
什么是申明文件
通常我們會把聲明語句放到一個單獨的文件 (jQuery.d.ts)
中, 這就是聲明文件
// jQuery.d.ts
declare var jQuery: ( selector: string ) => any
// src/index.ts
jQuery('#foo')
一般來說,ts 會解析項目中所有的 *.ts
文件,當然也包含以 .d.ts
結尾的文件。所以當我們將 jQuery.d.ts
放到項目中時,其他所有 *.ts
文件就都可以獲得 jQuery
的類型定義了。
假如仍然無法解析,那么可以檢查下 tsconfig.json
中的 files
、include
和 exclude
配置,確保其包含了 jQuery.d.ts
文件。
三斜線指令
三斜線指令 類似於聲明文件中的 import
,它可以用來導入另一個聲明文件。與 import
的區別是,當且僅當在以下幾個場景下,我們才需要使用三斜線指令替代 import
:
-
當我們在書寫一個全局變量的聲明文件時
-
當我們需要依賴一個全局變量的聲明文件時
這些場景聽上去很拗口,但實際上很好理解
場景一: 在全局變量的聲明文件中,是不允許出現 import
, export
關鍵字的。
場景二: 當我們需要依賴一個全局變量的聲明文件時,由於全局變量不支持通過 import
導入,當然也就必須使用三斜線指令來引入了
// types/node-plugin/index.d.ts
/// <reference types="node" />
export function foo(p: NodeJS.Process): string
// src/index.ts
import { foo } from 'node-plugin';
foo(global.process)
第三方聲明文件
jQuery 的聲明文件, 其實社區已經幫我做好了,jQuery in DefinitelyTyped。我們可以直接下載下來使用, 在使用時我們並不需要在去進行聲明
這里更加推薦的是 使用 @types
來統一管理第三庫的聲明文件。
以 jQuery 為例, 使用方式如下:
npm i @types/jquery
內置對象
JavaScript 中有很多內置對象,它們可以直接在 TypeScript 中當做定義好了的類型。
內置對象是指根據標准在全局作用域(Global)上存在的對象。這里的標准是指 ECMAScript 和其他環境(比如 DOM)的標准。