簡(jiǎn)介
TypeScript 是 JavaScript 的一個(gè)超集,主要提供了 類型系統(tǒng) 和對(duì) ES6 的支持,由 Microsoft 開發(fā)厚脉。
文檔地址:https://ts.xcatliu.com/basics/declaration-files
應(yīng)用:vue3.0攀细,angular2.0,vscode...
- 編譯型語(yǔ)言:編譯為 js 后運(yùn)行腹备,單獨(dú)無(wú)法運(yùn)行;
- 強(qiáng)類型語(yǔ)言;
- 面向?qū)ο蟮恼Z(yǔ)言;
優(yōu)勢(shì)
- 類型系統(tǒng)實(shí)際上是最好的文檔,大部分的函數(shù)看看類型的定義就可以知道如何使用斤蔓;
- 可以在編譯階段就發(fā)現(xiàn)大部分錯(cuò)誤箭跳,這總比在運(yùn)行時(shí)候出錯(cuò)好;
- 增強(qiáng)了編輯器和 IDE 的功能慨削,包括代碼補(bǔ)全旨涝、接口提示、跳轉(zhuǎn)到定義驾锰、重構(gòu)等卸留;
總結(jié):TypeSctipt增加了代碼的可讀性和可維護(hù)性。
安裝
需要有node環(huán)境椭豫,通過npm安裝
npm install -g typescript
編碼
使用 .ts
文件擴(kuò)展名耻瑟, 使用 typescript
編寫使用 React 時(shí),使用 .tsx
擴(kuò)展名赏酥。
使用 :
指定變量的類型喳整,:
的前后有沒有空格都可以;
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = 'Tom';
console.log(sayHello(user));
編譯
使用tsc
命令可編譯 .ts 文件, 生成一個(gè)同名 .js 文件裸扶;編譯的時(shí)候即使報(bào)錯(cuò)了框都,還是會(huì)生成編譯結(jié)果(.js),可通過 tsconfig.json 文件配置
tsc demo.ts
基礎(chǔ)類型
布爾值 boolean
let isDone: boolean = false;
注意呵晨,使用構(gòu)造函數(shù) Boolean 創(chuàng)造的對(duì)象不是布爾值
let newBool: boolean = new Boolean(true);
// 編譯報(bào)錯(cuò): 不能將類型“Boolean”分配給類型“boolean”魏保≌崽#“boolean”是基元,但“Boolean”是包裝器對(duì)象囱淋。如可能首選使用“boolean”猪杭。ts(2322)
數(shù)字 number
let number: number = 6;
let notANumber: number = NaN;
字符串 string
let string: string = 'Tom';
let sentence: string = `my name is ${aString}`;
空值 void
void 類型的變量只能賦值為 undefined 和 null
let unusable: void = undefined;
可以用 void 表示沒有任何返回值的函數(shù)
function alertName(): void {
alert('My name is Tom');
}
null 和 undefined
undefined 類型的變量只能被賦值為 undefined,null 類型的變量只能被賦值為 null
let u: undefined = undefined;
let n: null = null;
與 void 的區(qū)別是妥衣,undefined 和 null 是所有類型的子類型皂吮。也就是說 undefined 類型的變量,可以賦值給 number 類型的變量:
let u: undefined;
let num: number = u;
let num2:number = undefined;
// 編譯合法 undefined是number的子類型
let unm2: void;
let num3: number = unm2;
// => 不合法 (void不是number的子類型)
任意值 any
any 用來(lái)表示允許賦值為任意類型
let anyType:any = 'seven';
anyType = 7;
在任意值上訪問任何屬性和方法都是允許的税手,即不做類型檢查
let anyType:any = 'seven';
console.log(anyType.name().age)
// => 允許編譯蜂筹,但是js執(zhí)行會(huì)報(bào)錯(cuò)
變量如果在聲明的時(shí)候,未指定其類型芦倒, 也沒有賦值艺挪, 那么它會(huì)被推斷(類型推論)為任意值類型而完全不被類型檢查
let something;
// 等價(jià)于 let something: any;
something = 'seven';
something = 7;
數(shù)組
可理解為相同類型的一組數(shù)據(jù),數(shù)組類型有多種定義方式
1兵扬,類型 + 方括號(hào)( type [ ] )
這種方式定義的數(shù)組項(xiàng)中不允許出現(xiàn)其他的類型
let list: number[] = [1, 2, 3];
2麻裳,數(shù)組泛型 Array < type >
let list: Array<number> = [1, 2, 3];
元祖 Tuple
元組類型允許表示一個(gè)已知元素?cái)?shù)量和類型的數(shù)組,各元素的類型不必相同器钟,簡(jiǎn)單理解為可定義一組不同類型的數(shù)據(jù):
let arr:[string, number] = ['name', 20];
console.log(arr[0]);
// => 'name'
越界元素:當(dāng)訪問超出元祖長(zhǎng)度的元素時(shí)津坑,它的類型會(huì)被限制為元祖中每個(gè)類型的聯(lián)合類型
let arr:[string, number] = ['name', 20];
arr[0] = 'age';
arr[2] = 'string';
arr[3] = 40;
arr[4] = true; // 編譯報(bào)錯(cuò)
枚舉 enum
['en?m]
枚舉類型用于取值被限定在一定范圍內(nèi)的場(chǎng)景,如一周只有7天傲霸,一年只有4季等疆瑰。
枚舉初始化
枚舉初始化可以理解為給枚舉成員賦值。每個(gè)枚舉成員都需要帶有一個(gè)值昙啄,在未賦值的情況下穆役, 枚舉成員會(huì)被賦值為從 0
開始, 步長(zhǎng)為 1 遞增的數(shù)字:
enum Weeks {Mon, Tue, Wed, Thu, Fri, Sat, Sun};
console.log(Weeks['Mon']); // => 0
console.log(Weeks[0]); // => 'Mon'
console.log(Weeks.Tue); // => 1
手動(dòng)賦值時(shí)梳凛, 未賦值的枚舉成員會(huì)接著上一個(gè)枚舉項(xiàng)遞增(初始化):
enum Weeks {
Mon, Tue, Wed, Thu = 2, Fri, Sat = -1.5, Sun
};
console.log(Weeks['Mon']); // => 0
console.log(Weeks.Wed); // => 2
console.log(Weeks.Thu); // => 2
console.log(Weeks.Fri); // => 3
console.log(Weeks.Sun); // => -0.5
上例中耿币,未手動(dòng)賦值的 Wed 和手動(dòng)賦值的 Thu 取值重復(fù)了,但是 TypeScript 并不會(huì)報(bào)錯(cuò)伶跷,該種情況可能會(huì)引起取值錯(cuò)誤掰读,所以使用的時(shí)候最好避免出現(xiàn)取值重復(fù)的情況秘狞。
TypeScript 支持 數(shù)字 的和基于字符串的枚舉叭莫。
數(shù)字枚舉
enum Weeks {
Sun, Mon, Tue, Wed, Thu, Fri, Sat
};
字符串枚舉
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
異構(gòu)枚舉(Heterogeneous enums)
可以混合字符串和數(shù)字,但通常不這么做
enum Gender {
Male = 0,
Female = "1",
}
常量成員和計(jì)算所得成員
枚舉成員的值可以是 常量 或 計(jì)算出來(lái)的烁试。
上面所舉的例子都是常量成員雇初,官網(wǎng)定義如下:
當(dāng)滿足以下條件時(shí),枚舉成員被當(dāng)作是常數(shù):
- 不具有初始化函數(shù)并且之前的枚舉成員是常數(shù)减响。在這種情況下靖诗,當(dāng)前枚舉成員的值為上一個(gè)枚舉成員的值加
1
郭怪。但第一個(gè)枚舉元素是個(gè)例外。如果它沒有初始化方法刊橘,那么它的初始值為0
鄙才。 - 枚舉成員使用常數(shù)枚舉表達(dá)式初始化。常數(shù)枚舉表達(dá)式是 TypeScript 表達(dá)式的子集促绵,它可以在編譯階段求值攒庵。當(dāng)一個(gè)表達(dá)式滿足下面條件之一時(shí),它就是一個(gè)常數(shù)枚舉表達(dá)式:
- 數(shù)字字面量
- 引用之前定義的常數(shù)枚舉成員(可以是在不同的枚舉類型中定義的)如果這個(gè)成員是在同一個(gè)枚舉類型中定義的败晴,可以使用非限定名來(lái)引用
- 帶括號(hào)的常數(shù)枚舉表達(dá)式
-
+
,-
,~
一元運(yùn)算符應(yīng)用于常數(shù)枚舉表達(dá)式 -
+
,-
,*
,/
,%
,<<
,>>
,>>>
,&
,|
,^
二元運(yùn)算符浓冒,常數(shù)枚舉表達(dá)式做為其一個(gè)操作對(duì)象。若常數(shù)枚舉表達(dá)式求值后為 NaN 或 Infinity尖坤,則會(huì)在編譯階段報(bào)錯(cuò)
所有其它情況的枚舉成員被當(dāng)作是需要計(jì)算得出的值稳懒。
常量枚舉 const enum
常數(shù)枚舉與普通枚舉的區(qū)別是,它會(huì)在編譯階段被刪除慢味,并且不能包含計(jì)算成員场梆。
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
編譯后:
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
外部枚舉 declare enum
外部枚舉與聲明語(yǔ)句一樣,常出現(xiàn)在聲明文件中纯路。
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
編譯后:
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
同時(shí)使用 declare
和 const
也是可以的辙谜,編譯結(jié)果同常量枚舉一致。
never
永遠(yuǎn)不存在值的類型感昼,一般用于錯(cuò)誤處理函數(shù)装哆。
// 返回never的函數(shù)必須存在無(wú)法達(dá)到的終點(diǎn)
function error(message: string): never {
throw new Error(message);
}
symbol
自ECMAScript 2015起,symbol
成為了一種新的原生類型定嗓,就像 number
和 string
一樣蜕琴。
symbol
類型的值是通過Symbol
構(gòu)造函數(shù)創(chuàng)建的。
let sym1 = Symbol();
Symbols是不可改變且唯一的宵溅。
let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
更多用法參看 阮一峰ES6的symbol
object
object
表示非原始類型凌简,也就是除number
,string
恃逻,boolean
雏搂,symbol
,null
或undefined
之外的類型寇损。
function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
內(nèi)置對(duì)象
JavaScript 中有很多內(nèi)置對(duì)象凸郑,它們可以直接在 TypeScript 中當(dāng)做定義好了的類型。
ECMAScript 的內(nèi)置對(duì)象
Boolean
矛市、Error
芙沥、Date
、RegExp
等。更多的內(nèi)置對(duì)象而昨,可以查看 MDN 的文檔救氯。
let b: Boolean = new Boolean(1);
let e: Error = new Error('Error occurred');
let d: Date = new Date();
let r: RegExp = /[a-z]/;
DOM 和 BOM 的內(nèi)置對(duì)象
Document
、HTMLElement
歌憨、Event
着憨、NodeList
等。
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll('div');
document.addEventListener('click', function(e: MouseEvent) {
// Do something
});
類型推論
變量申明如果沒有明確的指定類型务嫡,那么 TypeScript 會(huì)依照類型推論的規(guī)則推斷出一個(gè)類型
let string = 'seven';
// 等價(jià)于 let string: string = 'seven';
string = 4;
// 編譯報(bào)錯(cuò): error TS2322: Type 'number' is not assignable to type 'string'
變量聲明但是未賦值享扔,會(huì)推論為 any
let x;
x = 1;
x = 'aaa'
聯(lián)合類型
表示取值可以為多種類型中的一種,使用 | 分隔每個(gè)類型
let stringOrNumber:string | number;
stringOrNumber = 'seven';
當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候, 我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法
function getString(something: string | number): string {
// toString 是 string類型 和 number類型 的共有屬性
return something.toString();
}
function getLength(something: string | number): number {
return something.length;
// => 編譯報(bào)錯(cuò): length 不是 string類型 和 number類型 的共有屬性, 所以報(bào)錯(cuò)
}
類型斷言
類型斷言(Type Assertion)可以用來(lái)手動(dòng)指定一個(gè)值的類型植袍。
類型斷言有2種形式:
1惧眠,<類型>值 ( 尖括號(hào)語(yǔ)法 )
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
2,值 as 類型 ( as 語(yǔ)法 )
當(dāng)使用 tsx
時(shí)于个,只有 as
語(yǔ)法斷言是被允許的
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
在上述 聯(lián)合類型 的例子中氛魁, getLength
方法會(huì)編譯報(bào)錯(cuò),此時(shí)我們可以使用類型斷言厅篓,將 something
斷言成 string
就不會(huì)報(bào)錯(cuò)了:
function getLength(something: string | number): number {
if ((<string>something).length) {
// 將 something 斷言為 string類型
return (<string>something).length;
} else {
return something.toString().length;
}
}
注意 : 類型斷言不是類型轉(zhuǎn)換秀存,斷言成一個(gè)聯(lián)合類型中不存在的類型是不允許的:
function toBoolean(something: string | number): boolean {
return <boolean>something;
// => 報(bào)錯(cuò)
}
類型別名 type
類型別名用來(lái)給一個(gè)類型起個(gè)新名字,多用于聯(lián)合類型:
type Name = string;
type GetName = () => string;
type NameOrGetter = Name | GetName;
function getName(n: NameOrGetter): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
type 聲明可以定義聯(lián)合類型羽氮,基本類型等多種類型或链,而 interface 只能定義對(duì)象類型
字符串字面量類型
字符串字面量類型用來(lái)約束取值只能是某幾個(gè)字符串中的一個(gè)。
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById('hello'), 'scroll'); // 沒問題
handleEvent(document.getElementById('world'), 'dbclick'); // 報(bào)錯(cuò)档押,event 不能為 'dbclick'
接口 Interfaces
接口(Interfaces)是一個(gè)很重要的概念澳盐,可以理解為一種規(guī)范或者約束,用來(lái)描述 對(duì)象(object) 的形狀 或者對(duì) 類(class) 的行為 進(jìn)行抽象令宿。對(duì)類的行為抽象將在后面 類與接口 一章中介紹叼耙,下面主要介紹對(duì)對(duì)象的形狀進(jìn)行描述。
接口定義
使用 interface 定義接口, 接口名稱一般首字母大寫粒没,定義接口的時(shí)候筛婉,只定義聲明即可,不包含具體內(nèi)容:
// 定義一個(gè)接口 Person
interface Person {
name: string;
age: number;
}
// 定義一個(gè)個(gè)變量癞松,它的類型是 Person
let tom: Person = {
name: 'Tom',
age: 25
};
實(shí)現(xiàn)接口的時(shí)候爽撒,要實(shí)現(xiàn)里面的內(nèi)容,定義的變量比接口少了或多了屬性都是不允許的:
let tom: Person = {
name: 'tom'
}
// => 編譯報(bào)錯(cuò)响蓉,少了age屬性
可選屬性
使用 ? 代表可選屬性, 即該屬性可以不存在, 但不允許添加未定義的屬性
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'tom'
}
// age是可選屬性
任意屬性
定義了任意屬性后可以添加未定義的屬性硕勿,并可以指定屬性值的類型
interface Person03 {
name: string;
age?: number;
[propName: string]: any;
}
let tom04: Person03 = {
name: 'Tom',
age: 25,
gender: 'male'
};
定義了任意屬性,那么確定屬性和可選屬性都必須是它的子屬性
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
// 編譯報(bào)錯(cuò):Person定義了一個(gè)任意屬性厕妖,其值為string類型首尼。則Person的所有屬性都必須為string類型,而age為number類型
只讀屬性 readonly
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
只讀的約束存在于第一次給對(duì)象賦值的時(shí)候言秸,而不是第一次給只讀屬性賦值的時(shí)候
let person: Person = {
id: 100,
name: 'tom',
}
person05.id = 90;
// => 編譯報(bào)錯(cuò):id為只讀, 不可修改
let person2: Person = {
name: 'welson',
age: 2
}
// => 編譯報(bào)錯(cuò):給對(duì)象 person2 賦值软能,未定義只讀屬性id
person2.id = 1;
// => 編譯報(bào)錯(cuò):id為只讀, 不可修改
函數(shù)類型接口
// 只有參數(shù)列表和返回值類型的函數(shù)定義, 參數(shù)列表里的每個(gè)參數(shù)都需要名字和類型
interface SearchFunc {
(source: string, subString: string): boolean;
}
函數(shù)
函數(shù)聲明
function sum(x: number, y: number): number {
return x + y;
}
輸入多余的(或者少于要求的)參數(shù),是不被允許的
sum(1, 2, 3);
// 編譯報(bào)錯(cuò):多了1個(gè)參數(shù)
匿名函數(shù)(函數(shù)表達(dá)式)
let mySum = function (x: number, y: number): number {
return x + y;
};
上面的代碼只對(duì)等號(hào)右側(cè)的匿名函數(shù)進(jìn)行了類型定義举畸,而等號(hào)左邊的 mySum
查排,是通過賦值操作進(jìn)行類型推論而推斷出來(lái)的。如果需要我們手動(dòng)給 mySum
添加類型抄沮,則應(yīng)該是這樣:
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
// 注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>
用接口定義函數(shù)的形狀
interface FuncAdd {
(value: number, increment: number): number
}
let add: FuncAdd;
add = function(value: number, increment: number): number {
return value + increment;
}
// 函數(shù)的參數(shù)名不需要與接口里定義的名字相匹配
let add2: FuncAdd;
add2 = function(a: number, b: number) {
return a + b;
}
可選參數(shù)
可選參數(shù)必須接在必需參數(shù)后面跋核,換句話說,可選參數(shù)后面不允許再出現(xiàn)必須參數(shù)了
function addNum(a: number, b: number, c? :number): number {
if(c) {
return a + b + c;
} else {
return a + b;
}
}
console.log(add(1, 2));
默認(rèn)參數(shù)
類比 ES6 中的默認(rèn)值
function add(a: number = 1, b: number): number {
return a + b;
}
console.log(add(undefined, 1));
剩余參數(shù)
類比 Es6 中對(duì)象展開
interface AddFunc {
(num1: number, ...rest: number[]): number
}
let add: AddFunc;
add = function(a: number, ...rest: number[]): number {
let result = a;
rest.map(v => result += v);
return result;
}
console.log(add(1,2,3,4));
函數(shù)重載
重載是為同一個(gè)函數(shù)提供多個(gè)函數(shù)類型定義叛买,允許函數(shù)對(duì)傳入不同的參數(shù)返回不同的的結(jié)果分別做類型檢查
比如實(shí)現(xiàn)一個(gè)數(shù)字或字符串的反轉(zhuǎn)函數(shù):
function reverse(text: number | string): number | string {
if(typeof text === 'string') {
return text.split('').reverse().join('');
} else if(typeof text === 'number') {
return +text.toString().split('').reverse().join('')
}
}
上述函數(shù)利用聯(lián)合類型實(shí)現(xiàn)砂代,但有一個(gè)缺點(diǎn),無(wú)法精確檢查輸入和輸出類型率挣,即輸入數(shù)字輸出也應(yīng)該為數(shù)字刻伊,這時(shí)就可以使用重載定義多個(gè)函數(shù)類型:
function reverse(text: number): number;
function reverse(text: string): string;
function reverse(text: number | string): number | string {
if(typeof text === 'string') {
return text.split('').reverse().join('');
} else if(typeof text === 'number') {
return +text.toString().split('').reverse().join('')
}
}
重復(fù)定義多次函數(shù) reverse
,前幾次都是函數(shù)定義椒功,最后一次是函數(shù)實(shí)現(xiàn)捶箱。
TypeScript與JavaScript的處理流程相似,它會(huì)查找重載列表动漾,從第一個(gè)重載定義開始匹配丁屎,如果匹配的話就使用這個(gè)定義,所以多個(gè)函數(shù)定義如果有包含關(guān)系旱眯,需要優(yōu)先把精確的定義寫在前面晨川。
類 class
同ES6 的 class
相關(guān)概念
- 類(Class):定義了一件事物的抽象特點(diǎn),包含它的屬性和方法
- 對(duì)象(Object):類的實(shí)例删豺,通過
new
生成 - 面向?qū)ο螅∣OP)的三大特性:封裝础爬、繼承、多態(tài)
- 封裝(Encapsulation):將對(duì)數(shù)據(jù)的操作細(xì)節(jié)隱藏起來(lái)吼鳞,只暴露對(duì)外的接口看蚜。外界調(diào)用端不需要(也不可能)知道細(xì)節(jié),就能通過對(duì)外提供的接口來(lái)訪問該對(duì)象赔桌,同時(shí)也保證了外界無(wú)法任意更改對(duì)象內(nèi)部的數(shù)據(jù)
- 繼承(Inheritance):子類繼承父類供炎,子類除了擁有父類的所有特性外,還有一些更具體的特性
- 多態(tài)(Polymorphism):由繼承而產(chǎn)生了相關(guān)的不同的類疾党,對(duì)同一個(gè)方法可以有不同的響應(yīng)音诫。比如
Cat
和Dog
都繼承自Animal
,但是分別實(shí)現(xiàn)了自己的eat
方法雪位。此時(shí)針對(duì)某一個(gè)實(shí)例竭钝,我們無(wú)需了解它是Cat
還是Dog
,就可以直接調(diào)用eat
方法,程序會(huì)自動(dòng)判斷出來(lái)應(yīng)該如何執(zhí)行eat
- 存取器(getter & setter):用以改變屬性的讀取和賦值行為
- 修飾符(Modifiers):修飾符是一些關(guān)鍵字香罐,用于限定成員或類型的性質(zhì)卧波。比如
public
表示公有屬性或方法 - 抽象類(Abstract Class):抽象類是供其他類繼承的基類,抽象類不允許被實(shí)例化庇茫。抽象類中的抽象方法必須在子類中被實(shí)現(xiàn)
- 接口(Interfaces):不同類之間公有的屬性或方法港粱,可以抽象成一個(gè)接口。接口可以被類實(shí)現(xiàn)(implements)旦签。一個(gè)類只能繼承自另一個(gè)類查坪,但是可以實(shí)現(xiàn)多個(gè)接口
類的定義
使用 class
定義類,使用 constructor
定義構(gòu)造函數(shù)宁炫。
通過 new
生成新實(shí)例的時(shí)候偿曙,會(huì)自動(dòng)調(diào)用構(gòu)造函數(shù)。
class Animal {
name:string; // 定義屬性
constructor(name) {
this.name = name; // 屬性賦值
}
sayHi() {
return `我叫 ${this.name}`;
}
}
let cat = new Animal('Tom');
console.log(cat.sayHi()); // 我叫 Tom
類的繼承
使用 extends
關(guān)鍵字實(shí)現(xiàn)繼承羔巢,子類中使用 super
關(guān)鍵字來(lái)調(diào)用父類的構(gòu)造函數(shù)和方法遥昧。
class Cat extends Animal {
color: string;
constructor(name, color) {
super(name); // 調(diào)用父類Animal的 constructor(name)
this.color = color
}
sayHi() {
// 調(diào)用父類的 sayHi();
return super.sayHi() + '我是一只'+ this.color + ' 色的貓朵纷,';
}
}
let c = new Cat('Tom', '橘黃'); // Tom
console.log(c.sayHi()); // 我叫 Tom炭臭,我是一只橘黃色的貓;
let cat2 = new Cat('Jerry');
cat2.color = '黑';
console.log(c.sayHi()); // 我叫 Jerry袍辞,我是一只黑色的貓鞋仍;
存取器
使用 getter 和 setter 可以改變屬性的賦值和讀取行為:
class Animal {
name:string;
constructor(name) {
this.name = name;
}
get name() {
return 'Jack';
}
set name(value) {
console.log('setter: ' + value);
}
}
let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack
實(shí)例屬性和方法
js中的屬性和方法:
// js中
function Person(name) {
this.name = name; // 實(shí)例屬性
this.eat = function(){ console.log('eat') }; // 實(shí)例方法
}
Person.age = 19; // 靜態(tài)屬性
Person.sleep = function(){ console.log('sleep') }; // 靜態(tài)方法
// 訪問實(shí)例方法和屬性:
var tom = new Person('tom');
console.log(tom.name) // tom
tom.eat();
tom.sleep() // error: tom.sleep is not a function
// 訪問靜態(tài)方法和屬性:
console.log(Person.age); // 19
Person.sleep();
Person.eat(); // error: Person.eat is not a function
ES6 中實(shí)例的屬性只能通過構(gòu)造函數(shù)中的 this.xxx
來(lái)定義:
class Animal {
constructor(){
this.name = 'tom';
}
eat() {}
}
let a = new Animal();
console.log(a.name); // tom
ES7 提案中可以直接在類里面定義:
// ts
class Animal {
name = 'tom';
eat() {}
}
let a = new Animal();
console.log(a.name); // Jack
靜態(tài)屬性和方法
ES7 提案中,可以使用 static
定義一個(gè)靜態(tài)屬性或方法搅吁。靜態(tài)方法不需要實(shí)例化威创,而是直接通過類來(lái)調(diào)用:
// ts
class Animal {
static num = 42;
static isAnimal(a) {
return a instanceof Animal;
}
}
console.log(Animal.num); // 42
let a = new Animal('Jack');
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
訪問修飾符
public
公有屬性或方法,可以在任何地方被訪問到谎懦,默認(rèn)所有的屬性和方法都是 public
的
private
私有屬性或方法肚豺,不能在聲明它的類的外部訪問,也不可以在子類中訪問
protected
受保護(hù)的屬性或方法界拦,它和 private
類似吸申,區(qū)別是它可以在子類中訪問
class Person {
public name:string;
private idCard:number;
protected phone:number;
constructor(name,idCard,phone) {
this.name = name;
this.idCard = idCard;
this.phone = phone;
}
}
let tom = new Person('tom',420000,13811110000);
console.log(tom.name) // tom
console.log(tom.idCard)
// error:Property 'idCard' is private and only accessible within class 'Person'.
console.log(tom.phone)
// error:Property 'phone' is protected and only accessible within class 'Person' and its subclasses
class Teacher extends Person {
constructor(name,idCard,phone) {
super(name,idCard,phone);
console.log(this.name)
console.log(this.phone)
console.log(this.idCard)
// error:Property 'idCard' is private and only accessible within class 'Person'.
}
}
多態(tài)
同一個(gè)父類的多個(gè)子類,可以有不同結(jié)果的同名方法:
class Person {
eat(){ console.log('eat') }
}
class A extends Person {
eat(){ console.log('A eat') }
}
class B extends Person {
eat(){ console.log('B eat') }
}
抽象類/抽象方法 abstract
abstract
用于定義抽象類和其中的抽象方法享甸。
- 抽象類是提供給其他類繼承的基類(父類)截碴,是不允許被實(shí)例化
- 抽象方法只能包含在抽象類中
- 子類繼承抽象類,必須實(shí)現(xiàn)抽象類中的抽象方法
abstract class Animal {
abstract eat(); // 抽象方法
// 普通方法
sleep(){
console.log('sleep')
}
}
let a = new Animal(); // 報(bào)錯(cuò)蛉威,抽象類不能被實(shí)例化
class Cat extends Animal {
eat(){
// 父類的eat方法必須被實(shí)現(xiàn)
console.log('eat')
}
}
類與接口
前面介紹了 接口 可以用來(lái)描述 對(duì)象(object)的形狀日丹,這一章主要介紹 接口 對(duì) 類(class)的行為 進(jìn)行抽象。
類實(shí)現(xiàn)接口 implements
實(shí)現(xiàn)(implements)是面向?qū)ο笾械囊粋€(gè)重要概念蚯嫌。一個(gè)類只能繼承自另一個(gè)類哲虾,不同類之間可能會(huì)有一些共有特性丙躏,提取多個(gè)類的共有特性,作為一個(gè)接口束凑,再用 implements
關(guān)鍵字來(lái)實(shí)現(xiàn)就可以大大提高面向?qū)ο蟮撵`活性晒旅。
舉例: 人是一個(gè)類,人需要吃東西湘今。動(dòng)物是一個(gè)類敢朱,動(dòng)物也需要吃東西剪菱。這種情況就可以把 吃東西 提取出來(lái)作為一個(gè)接口:
interface Ieat {
eat();
}
class Person implements Ieat{
eat(){}
}
class Animal implements Ieat {
eat(){}
}
一個(gè)類也可以實(shí)現(xiàn)多個(gè)接口:
interface Ieat {
eat();
}
interface Isleep {
sleep();
}
class Person implements Ieat, Isleep{
eat(){}
sleep() {}
}
接口繼承接口
interface Alarm {
alert();
}
interface LightableAlarm extends Alarm {
lightOn();
lightOff();
}
接口繼承類
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
混合類型
前面介紹了接口可以用來(lái)定義函數(shù)的形狀摩瞎,有時(shí)候,一個(gè)函數(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;
泛型
泛型(Generics)是指在定義函數(shù)孝常、接口或類的時(shí)候旗们,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性构灸。
打印number:
function printer(arr:number[]):void {
for(var item of arr) {
console.log(item)
}
}
printer([1,2,3,4])
打印字符串:
// 打印字符串
function printer1(arr:string[]):void {
for(var item of arr) {
console.log(item)
}
}
printer1(['a','b','c','d'])
使用 any 也可以通過編譯上渴,但是無(wú)法準(zhǔn)確定義返回值的類型,這個(gè)時(shí)候就可以使用泛型函數(shù)
泛型函數(shù)
在函數(shù)名后加上 <T>
(也可以是其他別的字母)喜颁,其中 T
用來(lái)指代輸入的類型稠氮,在函數(shù)內(nèi)部就可以使用這個(gè) T
類型。
function printer<T>(arr:T[]):void {
for(var item of arr) {
console.log(item)
}
}
// 指定具體類型調(diào)用
printer<string>(['a','b','c','d']);
// 調(diào)用時(shí)也可以直接讓ts自己做類型推論
printer([1,2,3,4]);
也可以同時(shí)使用多個(gè)類型參數(shù)
function swap<S,P>(tuple:[S,P]):[P,S] {
return [tuple[1], tuple[0]]
}
swap<string, number>(['a', 2])
泛型類
class arrayList<T> {
name: T;
list: T[] = [];
add(val:T):void {
this.list.push(val)
}
}
var arr = new arrayList<number>();
arr.add(1)
arr.add(2)
console.log(arr.list)
泛型接口
interface Iadd<T> {
(x:T,y:T):T;
}
var add:Tadd<number> = function(x:number,y:number):number {
return x + y
}
泛型約束
在函數(shù)內(nèi)部使用泛型變量的時(shí)候半开,由于事先不知道它是哪種類型隔披,所以不能隨意的操作它的屬性或方法
獲取一個(gè)參數(shù)的長(zhǎng)度:
function getLength<T>(arg:T):T {
console.log(arg.length) // error: Property 'length' does not exist on type 'T'
return arg;
}
上例中,泛型 T
不一定包含屬性 length
寂拆,所以編譯的時(shí)候報(bào)錯(cuò)了奢米,這時(shí)候就可以使用泛型約束,使用 extends
約束泛型 <T>
必須符合 Ilength
的形狀纠永,也就是必須包含 length
屬性:
interface Ilength {
length: number
}
function getLength<T extends Ilength>(arg:T):T {
console.log(arg.length)
return arg;
}
getLength('abcd') // 4
getLength(7) // error: Argument of type '7' is not assignable to parameter of type 'Ilength'.
多個(gè)參數(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 })
上例中鬓长,使用了兩個(gè)類型參數(shù),其中要求 T
繼承 U
尝江,這樣就保證了 U
上不會(huì)出現(xiàn) T
中不存在的字段涉波。
聲明文件 declare
當(dāng)使用第三方庫(kù)時(shí),我們需要引用它的聲明文件炭序,才能獲得對(duì)應(yīng)的代碼補(bǔ)全怠蹂、接口提示等功能。
聲明語(yǔ)句
假如我們使用第三方庫(kù) jQuery少态,來(lái)獲取一個(gè)元素
$('#foo');
jQuery('#foo');
但是在 ts 中城侧,編譯器并不知道 $
或 jQuery
是什么東西:
jQuery('#foo');
// ERROR: Cannot find name 'jQuery'.
這時(shí),我們需要使用 declare var
來(lái)定義它的類型彼妻,declare var
并沒有真的定義一個(gè)變量嫌佑,只是定義了全局變量 jQuery
的類型豆茫,僅僅會(huì)用于編譯時(shí)的檢查,在編譯結(jié)果中會(huì)被刪除屋摇。
declare var jQuery: (selector: string) => any;
jQuery('#foo');
聲明文件
通常我們會(huì)把聲明語(yǔ)句放到一個(gè)單獨(dú)的文件(xxx.d.ts
)中揩魂,這就是聲明文件,聲明文件必需以 .d.ts
為后綴炮温。
一般來(lái)說火脉,ts 會(huì)解析項(xiàng)目中所有的 *.ts
文件,當(dāng)然也包含以 .d.ts
結(jié)尾的文件柒啤。所以當(dāng)我們將 jQuery.d.ts
放到項(xiàng)目中時(shí)倦挂,其他所有 *.ts
文件就都可以獲得 jQuery
的類型定義了。
這是使用全局變量模式的聲明文件担巩,還有其他模式如 模塊導(dǎo)入 等會(huì)在后面介紹方援。
第三方聲明文件
社區(qū)已經(jīng)幫我們定義好了很多第三方庫(kù)的聲明文件,可以直接下載下來(lái)使用涛癌,更推薦使用 @types
統(tǒng)一管理第三方庫(kù)的聲明文件犯戏。@types
的使用方式很簡(jiǎn)單,直接用 npm 安裝對(duì)應(yīng)的聲明模塊即可拳话,以 jQuery 舉例:
npm install @types/jquery --save-dev
可以在這個(gè)頁(yè)面搜索你需要的聲明文件先匪。
書寫聲明文件
當(dāng)一個(gè)第三方庫(kù)沒有提供聲明文件時(shí),我們就需要自己書寫聲明文件了弃衍。
在不同的場(chǎng)景下呀非,聲明文件的內(nèi)容和使用方式會(huì)有所區(qū)別:
- 全局變量:通過
<script>
標(biāo)簽引入第三方庫(kù),注入全局變量 - npm 包:通過
import foo from 'foo'
導(dǎo)入笨鸡,符合 ES6 模塊規(guī)范 - UMD 庫(kù):既可以通過
<script>
標(biāo)簽引入姜钳,又可以通過import
導(dǎo)入 - 模塊插件:通過
import
導(dǎo)入后,可以改變另一個(gè)模塊的結(jié)構(gòu) - 直接擴(kuò)展全局變量:通過
<script>
標(biāo)簽引入后形耗,改變一個(gè)全局變量的結(jié)構(gòu)哥桥。比如為String.prototype
新增了一個(gè)方法 - 通過導(dǎo)入擴(kuò)展全局變量:通過
import
導(dǎo)入后,可以改變一個(gè)全局變量的結(jié)構(gòu)
作者:MrWelson