來源 |?https://juejin.cn/post/6897779423858737166接口
例如我們這定義一個(gè)叫做printPost的函數(shù),那這個(gè)函數(shù)可以接收一個(gè)文章對(duì)象參數(shù)post,然后在函數(shù)的內(nèi)部去打印文章的title, 然后再去打印他的content屬性。function printPost (post) { console.log(post.title); console.log(post.content);}
那這個(gè)時(shí)候?qū)τ谶@個(gè)函數(shù)所接收的post對(duì)象他就有一定的要求,也就是我們所傳入的這個(gè)對(duì)象必須要存在一個(gè)title屬性和一個(gè)content屬性,只不過這種要求他實(shí)際上是隱性的,他沒有明確的表達(dá)出來。那這種情況下我們就可以使用接口去表現(xiàn)出來這種約束,這里我們可以嘗試先去定義一個(gè)接口。定義接口的方式呢就是使用interface這樣一個(gè)關(guān)鍵詞,然后后面跟上接口的名稱,這里我們可以叫做post,然后就是一對(duì){},然后{}里面就可以添加具體的成員限制。這里我們添加一個(gè)title和content,類型都是string。interface Post { title: string; content: string;}
注意這里我們可以使用逗號(hào)分割成員,但是更標(biāo)準(zhǔn)的做法是使用分號(hào)去分割,而且呢這個(gè)分號(hào)跟js中絕大多數(shù)的分號(hào)是一樣的,可以省略,那關(guān)于是否應(yīng)該在代碼當(dāng)中明確使用每一個(gè)分號(hào),個(gè)人的編碼習(xí)慣是不加,你可以根據(jù)你所在的團(tuán)隊(duì)或者是項(xiàng)目對(duì)應(yīng)的編碼規(guī)范來去決定要不要加分號(hào),這個(gè)問題我們不做過多討論。完成過后我們這里可以給這個(gè)post參數(shù)的類型設(shè)置為我們剛剛所定義的Post接口。function printPost (post: Post) { console.log(post.title); console.log(post.content);}
printPost({ title: 'hello', content: 'typescript'})
那此時(shí)就是顯示的要求我們所傳入的對(duì)象他必須要有title和content這兩個(gè)成員了,那這就是接口的一個(gè)基本作用。一句話去總結(jié),接口就是用來約束對(duì)象的結(jié)構(gòu),那一個(gè)對(duì)象去實(shí)現(xiàn)一個(gè)接口,他就必須要去擁有這個(gè)接口當(dāng)中所約束的所有的成員。我們可以編譯一下這個(gè)代碼,編譯過后我們打開對(duì)應(yīng)的js文件,我們?cè)趈s當(dāng)中并不會(huì)發(fā)現(xiàn)有任何跟接口相關(guān)的代碼,也就是說TypeScript中的接口他只是用來為我們有結(jié)構(gòu)的數(shù)據(jù)去做類型約束的,在實(shí)際運(yùn)行階段呢,實(shí)際這種接口他并沒有意義。可選成員,只讀成員
對(duì)于接口中約定的成員,還有一些特殊的用法,我們依次來看一下。首先是可選成員,如果說我們?cè)谝粋€(gè)對(duì)象當(dāng)中,我們某一個(gè)成員他是可有可無的話,那這樣的話我們對(duì)于約束這個(gè)對(duì)象的接口來說我們可以使用可選成員這樣一個(gè)特性。例如我們這里添加一個(gè)subTitle這樣一個(gè)成員,他的類型同樣是string,不過我們這里的文章不一定是每一個(gè)都有subTitle,這種況下我們就可以在subTitle后面添加一個(gè)問號(hào),這就表示我們這個(gè)subTitle成員他是可有可無的了。interface Post { title: string; content: string; subTitle?: string;}
那這種用法呢其實(shí)就是相當(dāng)于給這個(gè)subTitle標(biāo)記他的類型是string或者是undefined。這就是可選成員。接下來我們?cè)賮砜匆幌轮蛔x成員這樣一個(gè)特性,那這里我們?cè)俳oPost接口添加一個(gè)summary這樣一個(gè)成員,那一般邏輯上來講的話文章的summary他都是從文章的內(nèi)容當(dāng)中自動(dòng)提取出來的,所以說我們不應(yīng)該允許外界去設(shè)置他。那這種情況下我們可以使用readonly這樣一個(gè)關(guān)鍵詞,去修飾一下這里的summary。那添加了readonly過后我們這個(gè)summary他在初始化完成過后就不能夠再去修改了。如果我們?cè)偃バ薷木蜁?huì)報(bào)錯(cuò)。這就是只讀成員。interface Post { title: string; content: string; subTitle?: string; readonly summary: string;}
最后我們?cè)賮砜匆粋€(gè)動(dòng)態(tài)成員的用法,那這種用法一般是適用于一些具有動(dòng)態(tài)成員對(duì)象,例如程序當(dāng)中的緩存對(duì)象,那他在運(yùn)行過程中就會(huì)出現(xiàn)一些動(dòng)態(tài)的鍵值。這里我們來新建一個(gè)新的接口,因?yàn)槲覀冊(cè)诙x的時(shí)候我們是無法知道會(huì)有那些具體的成員,所以說我們就不能夠去指定,具體的成員名稱,而是使用一個(gè)[], 這個(gè)[]中使用key: string。這個(gè)key并不是固定的,可以是任意的名稱, 只是代表了我們屬性的名稱,他是一個(gè)格式,然后后面這個(gè)string就是成員名的類型,也就是鍵的類型,后面我們可以跟上動(dòng)態(tài)屬性的值為string。interface Cache { [key: string]: string;}
完成以后我們?cè)賮韯?chuàng)建一個(gè)cache對(duì)象,讓他去實(shí)現(xiàn)這個(gè)接口,那這個(gè)時(shí)候我們就可以在這個(gè)cache對(duì)象上動(dòng)態(tài)的去添加任意的成員了, 只不過這些成員他都必須是stringd類型的鍵值。const cache: Cache = {};
cache.foo = 'value1';cache.bar = 'value2'
類的基本使用
類可以說是面向?qū)ο缶幊讨幸粋€(gè)最重要的概念,關(guān)于類的作用這里我們?cè)俸唵蚊枋鲆幌隆K褪怯脕砻枋鲆活惥唧w事物的抽象特征,我們可以以生活角度去舉例。例如手機(jī)就屬于一個(gè)類型,那這個(gè)類型的特征呢就是能夠打電話,發(fā)信息。那在這個(gè)類型下面呢他還會(huì)有一些細(xì)分的子類,那這種子類他一定會(huì)滿足父類的所有特征,然后再多出來一些額外的特征。例如只能手機(jī),他除了可以打電話發(fā)短信還能夠使用一些app。那我們是不能直接去使用類的,而是去使用這個(gè)類的具體事物,例如你手中的只能手機(jī)。那類比到程序的角度,類也是一樣的,他可以用來去描述一類具體對(duì)象的一些抽象成員,那在ES6以前,JavaScript都是通過函數(shù)然后配合原型去模擬實(shí)現(xiàn)的類。那從ES6開始,JavaScript中有了專門的class。而在TypeScript中,我們除了可以使用所有ECMAScript的標(biāo)準(zhǔn)當(dāng)中所有類的功能,他還添加了一些額外的功能和用法,例如我們對(duì)類成員有特殊的訪問修飾符,還有一些抽象類的概念。那對(duì)于ECMAScript標(biāo)準(zhǔn)中的class,我們這里就不單獨(dú)去介紹了,如果你不太熟悉,你可以去參考ECMAScript前面的文章。這里我們來著重介紹一下,在TypeScript中額外多出來的一些新的類的一些特性,我們可以先來聲明一個(gè)叫做Person的類型,然后我們?cè)谶@個(gè)類型當(dāng)中去聲明一下constructor構(gòu)造桉樹,在構(gòu)造函數(shù)當(dāng)中我們接收一個(gè)name和age參數(shù),那這里我們?nèi)匀豢梢允褂妙愋妥⒔獾姆绞饺?biāo)注我們這個(gè)地方每個(gè)參數(shù)的類型。然后在這個(gè)構(gòu)造函數(shù)的里面我們可以使用this去為當(dāng)前這個(gè)類型的屬性去賦值,不過這里直接去使用this訪問當(dāng)前類的屬性會(huì)報(bào)錯(cuò),說的是當(dāng)前這個(gè)Person類型上面并不存在對(duì)應(yīng)的name和age。class Person { constructor(name: string, age: number) { this.name = name; this.age = age; }}
這是因?yàn)樵赥ypeScript中我們需要明確在類型中取聲明他所擁有的一些屬性,而不是直接在構(gòu)造函數(shù)當(dāng)中動(dòng)態(tài)通過this去添加。那在類型聲明屬性的方式就是直接在類當(dāng)中去定義, 那這個(gè)語法呢是ECMAScript2016標(biāo)準(zhǔn)當(dāng)中定義的,那我們同樣可以在這里給name和gae屬性添加類型。那他也可以通過等號(hào)去直接賦值一個(gè)初始值,不過一般情況下我們還是會(huì)在構(gòu)造函數(shù)中動(dòng)態(tài)的為屬性賦值。需要注意的是,在TypeScript中類的屬性他必須要有一個(gè)初始值,可以在等號(hào)后面去賦值,或者是在構(gòu)造函數(shù)當(dāng)中去初始化,兩者必須做其一,否則就會(huì)報(bào)錯(cuò)。class Person { name: string = 'init name'; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; }}
那以上就是在TypeScript中類屬性在定義上的一些細(xì)微差異,其實(shí)具體來說就是我們類的屬性他在使用之前必須要現(xiàn)在類型當(dāng)中去聲明,那這么做的目的其實(shí)就是為了給我們的屬性去做一些類型的標(biāo)注。那除此之外呢我們?nèi)匀豢梢园凑誆S6標(biāo)準(zhǔn)當(dāng)中的語法,為這個(gè)類型去聲明一些方法,例如我們這里添加一個(gè)叫做sayHi的方法,那在這個(gè)方法當(dāng)中我們?nèi)匀豢梢允褂煤瘮?shù)類型注解的方式去限制參數(shù)的類型和返回值的類型。那在這個(gè)方法的內(nèi)部呢我們同樣可以使用this去訪問當(dāng)前實(shí)例對(duì)象,也就可以訪問到對(duì)應(yīng)的屬性。class Person { name: string = 'init name'; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; }
sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
那以上就是類在TypeScript中的一個(gè)基本使用。類的訪問修飾符
接下來我們?cè)賮砜磶讉€(gè)TypeScript中類的一些特殊用法,那首先就是類當(dāng)中成員的訪問修飾符,類中的每一個(gè)成員都可以使用訪問修飾符去修飾他們。例如我們這里給age屬性前面去添加一個(gè)private,表示這個(gè)age屬性是一個(gè)私有屬性,這種私有屬性只能夠在類的內(nèi)部去訪問,這里我們創(chuàng)建一個(gè)Person對(duì)象, 我們打印tom的name屬性和age屬性。可以發(fā)現(xiàn)name可以訪問,age就會(huì)報(bào)錯(cuò),因?yàn)閍ge已經(jīng)被我們標(biāo)記為了私有屬性。class Person { name: string = 'init name'; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; }
sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
const tom = new Person('tom', 18);
console.log(tom.name);console.log(tom.age);
除了private以外,我們還可以使用public修飾符去修飾成員,意思是他是一個(gè)共有成員,不過再TypeScript中,類成員的訪問修飾符默認(rèn)就是public,所以我們這里加不加public效果都是一樣的。不過我們還是建議大家手動(dòng)去加上這種public的修飾符,因?yàn)檫@樣的話,我們的代碼會(huì)更加容易理解一點(diǎn)。class Person { public name: string = 'init name'; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; }
sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
最后還有一個(gè)叫做protected修飾符,說的就是受保護(hù)的,我們可以添加一個(gè)gender的屬性,他的訪問修飾符我們就使用protected,我們同樣在構(gòu)造函數(shù)中初始化一下gender。完成過后我們?cè)趯?shí)例對(duì)象上去訪問gender,會(huì)發(fā)現(xiàn)也是訪問不到的,也就是protected也不能在外部直接訪問。class Person { public name: string = 'init name'; private age: number; protected gender: boolean; constructor(name: string, age: number) { this.name = name; this.age = age; this.gender = true; }
sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); }}
const tom = new Person('tom', 18);
console.log(tom.name);console.log(tom.gender);
那他跟private的區(qū)別到底在什么地方呢, 這里我們可以再定義一個(gè)叫做Student的類型,我們讓這個(gè)類型去繼承自Person,我們?cè)跇?gòu)造函數(shù)中嘗試訪問父類的gender,是可以訪問到的。那意思就是protected只允許在子類當(dāng)中去訪問對(duì)應(yīng)的成員。class Student extends Person { constructor(name: string, age: number) { super(name, age); console.log(this.gender); }}
那以上就是TypeScript當(dāng)中對(duì)于類額外添加的三個(gè)訪問修飾符。分別是private,protected和public,那他們的作用可以用來去控制類當(dāng)中的成員的可訪問級(jí)別。那這里還有一個(gè)需要注意的點(diǎn),就是對(duì)于構(gòu)造函數(shù)的訪問修飾符,那構(gòu)造函數(shù)的訪問修飾符默認(rèn)也是public,那如果說我們把它設(shè)置為private,那這個(gè)類型就不能夠在外部被實(shí)例化了,也不能夠被繼承,那在這樣一種情況下,我們就只能夠在這個(gè)類的內(nèi)部去添加一個(gè)靜態(tài)方法,然后在靜態(tài)方法當(dāng)中去創(chuàng)建這個(gè)類型的實(shí)例,因?yàn)閜rivate只允許在內(nèi)部訪問。例如我們這里再去添加一個(gè)叫create的靜態(tài)方法,那static也是ES6標(biāo)準(zhǔn)當(dāng)中定義的,然后我們就可以在這個(gè)create方法中去使用new的方式去創(chuàng)建這個(gè)類型的實(shí)例,因?yàn)閚ew的方式就是調(diào)用了這個(gè)類型的構(gòu)造函數(shù)。此時(shí)我們就可以在外部去使用create靜態(tài)方法創(chuàng)建Student類型的對(duì)象了。class Student extends Person { private constructor(name: string, age: number) { super(name, age); console.log(this.gender); }
static create(name: string, age: number) { return new Student(name, age); }}
const jack = Student.create('jack', 18);
那如果我們把構(gòu)造函數(shù)標(biāo)記為protected,這樣一個(gè)類型也是不能在外面被實(shí)例化的,但是相比于private他是允許繼承的,這里我們就不單獨(dú)演示了。類的只讀屬性
對(duì)屬性成員我們除了可以使用private和protected去控制它的訪問級(jí)別,我們還可以使用一個(gè)叫做readonly的關(guān)鍵詞去把這個(gè)成員設(shè)置為只讀的。這里我們將gender屬性設(shè)置為readonly,注意這里如果說我們的屬性已經(jīng)有了訪問修飾符的話,那readonly應(yīng)該跟在訪問修飾符的后面,對(duì)于只讀屬性,我們可以選擇在類型聲明的時(shí)候直接通過等號(hào)的方式去初始化,也可以在構(gòu)造函數(shù)當(dāng)中去初始化,二者只能選其一。也就是說我們不能在聲明的時(shí)候初始化,然后在構(gòu)造函數(shù)中修改它,因?yàn)檫@樣的話已經(jīng)破壞了readonly。class Person { public name: string = 'init name'; private age: number; protected readonly gender: boolean; constructor(name: string, age: number) { this.name = name; this.age = age; this.gender = true; }
sayHi(msg: string): void { console.log(`I am ${this.name}, ${msg}`); console.log(this.age); }}
const tom = new Person('tom', 18);
console.log(tom.name);
在初始化過后呢, 這個(gè)gender屬性就不允許再被修改了,無論是在內(nèi)部還是外部,他都是不允許修改的。以上就是readonly這樣一個(gè)只讀屬性,還是比較好理解的。類與接口
相比于類,接口的概念要更為抽象一點(diǎn),我們可以接著之前所說的手機(jī)的例子來去做比。我們說手機(jī)他是一個(gè)類型,這個(gè)實(shí)例的類型都是能夠打電話,發(fā)短信的,因?yàn)槭謾C(jī)這個(gè)類的特征呢就是打電話,發(fā)短信。但是呢,我們能夠打電話的,不僅僅只有手機(jī),在以前還有比較常見的座機(jī),也能夠打電話,但是座機(jī)并不屬于手機(jī)這個(gè)類目,而是一個(gè)單獨(dú)的類目,因?yàn)樗荒軌虬l(fā)短信,也不能夠拿著到處跑。那在這種情況下就會(huì)出現(xiàn),不同的類與類之間也會(huì)有一些共同的特征,那對(duì)于這些公共的特征我們一般會(huì)使用接口去抽象,那你可以理解為手機(jī)也可以打電話,因?yàn)樗麑?shí)現(xiàn)了能夠打電話的協(xié)議,而座機(jī)也能夠打電話因?yàn)樗矊?shí)現(xiàn)了這個(gè)相同的協(xié)議。那這里所說的協(xié)議呢我們?cè)诔绦虍?dāng)中就叫做接口,當(dāng)然如果說你是第一次接受這種概念的話,那可能理解起來會(huì)有些吃力,個(gè)人的經(jīng)驗(yàn)就是多思考,多從生活的角度去想,如果實(shí)在想不通,更粗暴的辦法就是不斷的去用,用的過程當(dāng)中慢慢的去總結(jié)規(guī)律時(shí)間長了自然也就好了。class Person { eat (food: string): void {} run (distance: number) {}}
class Animal { eat (food: string): void {} run (distance: number) {}}
這里我們來看一個(gè)例子,我們定義好了兩個(gè)類型,分別是Person和Animal,也就是人類和動(dòng)物類,那他們實(shí)際上是兩個(gè)完全不同的類型,但是他們之間也會(huì)有一些相同的特性。例如他們都會(huì)吃東西都會(huì)跑。那這種情況下就屬于不同的類型實(shí)現(xiàn)了一個(gè)相同的接口,那可能有人會(huì)問,我們?yōu)槭裁床唤o他們之間抽象一個(gè)公共的父類,然后把公共的方法都定義到父類當(dāng)中。那這個(gè)原因也很簡單,雖然人和動(dòng)物都會(huì)吃,都會(huì)跑,但是我們說人吃東西和狗吃東西能是一樣的么,那他們只是都有這樣的能力,而這個(gè)能力的實(shí)現(xiàn)肯定是不一樣的。那在這種情況下我們就可以使用接口去約束這兩個(gè)類型之間公共的能力,我們定義一個(gè)接口叫做EatAndRun,然后我們?cè)谶@個(gè)接口當(dāng)中分別去添加eat和run這兩個(gè)方法成員的約束。那這里我們就需要使用這種函數(shù)簽名的方式去約束這兩個(gè)方法的類型,而我們這里是不做具體的方法實(shí)現(xiàn)的。interface EatAndRun { eat (food: string): void; run (distance: number): void;}
那有了這個(gè)接口過后呢,我們?cè)俚絇erson類型后面,我們使用implements實(shí)現(xiàn)以下這個(gè)EatAndRun接口, 那此時(shí)在我們這個(gè)類型中就必須要有對(duì)應(yīng)的成員,如果沒有就會(huì)報(bào)錯(cuò),因?yàn)槲覀儗?shí)現(xiàn)這個(gè)接口就必須有他對(duì)應(yīng)的成員。class Person implements EatAndRun { eat (food: string): void {} run (distance: number) {}}
那這里我們需要注意的一點(diǎn)是,在C#和Java這樣的一些語言當(dāng)中他建議我們盡可能讓每一個(gè)接口的定義更加簡單,更加細(xì)化。就是我們EatAndRun這樣一個(gè)接口當(dāng)中我們抽象了兩個(gè)方法,那就相當(dāng)于抽象了兩個(gè)能力,那這兩個(gè)能力必然會(huì)同時(shí)存在么?是不一定的。例如說摩托車也會(huì)跑,但是就不會(huì)吃東西,所以說我們更為合理的就是一個(gè)接口只去約束一個(gè)能力,然后讓一個(gè)類型同時(shí)去實(shí)現(xiàn)多個(gè)接口,那這樣的話會(huì)更加合理一些,那我們這里可以把這個(gè)接口拆成一個(gè)Eat接口和Run接口,每個(gè)接口只有一個(gè)成員。然后我們就可以在類型的后面使用逗號(hào)的方式,同時(shí)去實(shí)現(xiàn)Eat和Run這兩個(gè)接口。interface Eat { eat (food: string): void;}
interface Run { run (distance: number): void;}
class Person implements Eat, Run { eat (food: string): void {} run (distance: number) {}}
那以上呢就是用接口去對(duì)類進(jìn)行一些抽象,那這里再多說一句題外話,就是大家千萬不要把自己框死在某一門語言或者是技術(shù)上面,最好可以多接觸,多學(xué)習(xí)一些周邊的語言或者技術(shù),因?yàn)檫@樣的話可以補(bǔ)充你的知識(shí)體系。那最簡單來說,一個(gè)只了解JavaScript的開發(fā)人員,即便說他對(duì)JavaScript有多么精通,那他也不可能設(shè)計(jì)出一些比較高級(jí)的產(chǎn)品。例如我們現(xiàn)在比較主流的一些框架,他們大都采用一些MVVM的這樣一些思想,那這些思想呢他實(shí)際上最早出現(xiàn)在微軟的WPS技術(shù)當(dāng)中的,如果說你有更寬的知識(shí)面的話,那你可以更好的把多家的思想融合到一起,所以說我們的視野應(yīng)該放寬一點(diǎn)。抽象類
最后我們?cè)賮砹私庖幌鲁橄箢悾浅橄箢愒谀撤N程度上來說跟接口有點(diǎn)類似,那他也是用來約束子類當(dāng)中必須要有某一個(gè)成員。但是不同于接口的是,抽象類他可以包含一些具體的實(shí)現(xiàn),而接口他只能夠是一個(gè)成員的一個(gè)抽象,他不包含具體的實(shí)現(xiàn)。那一般比較大的類目我們都建議大家使用抽象類,例如我們剛剛所說的動(dòng)物類,那其實(shí)他就應(yīng)該是抽象的,因?yàn)槲覀兯f的動(dòng)物他只是一個(gè)泛指,他并不夠具體,那在他的下面一定會(huì)有一些更細(xì)化的劃分,比如說小狗,小貓之類的。而且我們?cè)谏町?dāng)中一般都會(huì)說我們買了一條狗,或者說買了一只貓,從來沒有人說我們買了一個(gè)動(dòng)物。abstract class Animal { eat (food: string): void {}}
我們有一個(gè)Animal類型,那他應(yīng)該像我們剛剛說的那樣,被定義成抽象類,定義抽象類的方式就是在class關(guān)鍵詞前面添加一個(gè)abstract,那這個(gè)類型被定義成抽象類過后,他就只能夠被繼承,不能夠再去實(shí)例化。在這種情況下我們就必須使用子類去繼承這個(gè)抽象類, 這里我們定義一個(gè)叫做Dog的類型, 然后我們讓他繼承自Animal,那在抽象類當(dāng)中我們還可以去定義一些抽象方法,那這種抽象方法我們可以使用abstract關(guān)鍵詞來修飾,我們這里定義一個(gè)叫做run的抽象方法, 需要注意的是抽象方法也不需要方法體.當(dāng)父類中有抽象方法時(shí),我們的子類就必須要去實(shí)現(xiàn)這個(gè)方法。那此時(shí)我們?cè)偃ナ褂眠@個(gè)子類所創(chuàng)建的對(duì)象時(shí),就會(huì)同時(shí)擁有父類當(dāng)中的一些實(shí)例方法以及自身所實(shí)現(xiàn)的方法。那這就是抽象類的基本使用。abstract class Animal { eat (food: string): void {} abstract run (distance: number): void;}
class Dog extends Animal { run (distance: number): void {}}
關(guān)于抽象類呢,更多的還是去理解他的概念,他在使用上并沒有什么復(fù)雜的地方。泛型
泛型(Generics)是指在定義函數(shù)、接口或者類的時(shí)候, 不預(yù)先指定其類型,而是在使用是手動(dòng)指定其類型的一種特性。比如我們需要?jiǎng)?chuàng)建一個(gè)函數(shù), 這個(gè)函數(shù)會(huì)返回任何它傳入的值。function identity(arg: any): any { return arg}
identity(3)
這代代碼編譯不會(huì)出錯(cuò),但是存在一個(gè)顯而易見的缺陷, 就是沒有辦法約束輸出的類型與輸入的類型保持一致。這時(shí),我們可以使用泛型來解決這個(gè)問題;function identity<T>(arg: T): T { return arg}
identity(3)
我們?cè)诤瘮?shù)名后面加了 , 其中的 T 表示任意輸入的類型, 后面的 T 即表示輸出的類型,且與輸入保持一致。當(dāng)然我們也可以在調(diào)用時(shí)手動(dòng)指定輸入與輸出的類型, 如上述函數(shù)指定 string 類型:在泛型函數(shù)內(nèi)部使用類型變量時(shí), 由于事先并不知道它是那種類型, 所以不能隨意操作它的屬性和方法:function loggingIdentity<T>(arg: T): T { console.log(arg.length) return arg}
上述函數(shù)中 類型 T 上不一定存在 length 屬性, 所以編譯的時(shí)候就報(bào)錯(cuò)了。這時(shí),我們可以的對(duì)泛型進(jìn)行約束,對(duì)這個(gè)函數(shù)傳入的值約束必須包含 length 的屬性, 這就是泛型約束:interface lengthwise { length: number}
function loggingIdentity<T extends lengthwise>(arg: T): T { console.log(arg.length) return arg}
loggingIdentity({a: 1, length: 1}) loggingIdentity('str') loggingIdentity(6)
這樣我們就可以通過泛型約束的方法對(duì)函數(shù)傳入的參數(shù)進(jìn)行約束限制。多個(gè)參數(shù)時(shí)也可以在泛型約束中使用類型參數(shù) 如你聲明了一個(gè)類型參數(shù), 它被另一類型參數(shù)所約束。現(xiàn)在想要用屬性名從對(duì)象里湖區(qū)這個(gè)屬性。并且還需確保這個(gè)屬性存在于這個(gè)對(duì)象上, 因此需要咋這兩個(gè)類型之間使用約束,簡單舉例來說:定義一個(gè)函數(shù), 接受兩個(gè)參數(shù) 第一個(gè)是個(gè)對(duì)象 obj,第二個(gè)個(gè)參數(shù)是第一參數(shù) key 是對(duì)象里面的鍵名, 需要輸入 obj[key]function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]}
let obj = { a: 1, b: 2, c: 3 }
getProperty(obj, 'a') getProperty(obj, 'm')
我們可以為泛型中的類型參數(shù)指定默認(rèn)類型。當(dāng)使用泛型時(shí)沒有在代碼中直接指定類型參數(shù),從實(shí)際值參數(shù)中也無法推測(cè)出時(shí),這個(gè)默認(rèn)類型就會(huì)起作用function createArr<T = string>(length: number, value: T): Array<T> { let result: T[] = [] for( let i = 0; i < lenght; i++ ) { result[i] = value } return result}
簡單來說,泛型就是把我們定義時(shí)不能夠明確的類型變成一個(gè)參數(shù),讓我們?cè)谑褂玫臅r(shí)候再去傳遞這樣一個(gè)類型參數(shù)。類型聲明
在項(xiàng)目開發(fā)過程中我們難免會(huì)用到一些第三方的npm模塊,而這些npm模塊他不一定都是通過TypeScript編寫的,所以說他所提供的成員呢就不會(huì)有強(qiáng)類型的體驗(yàn)。比如我們這里安裝一個(gè)lodash的模塊,那這個(gè)模塊當(dāng)中就提供了很多工具函數(shù),安裝完成過后我們回到代碼當(dāng)中,我們使用ES Module的方式import導(dǎo)入這個(gè)模塊,我們這里導(dǎo)入的時(shí)候TypeScript就已經(jīng)報(bào)出了錯(cuò)誤,找不到類型聲明的文件,我們暫時(shí)忽略。這里我們提取一下camelCase的函數(shù),那這個(gè)函數(shù)的作用就是把一個(gè)字符串轉(zhuǎn)換成駝峰格式,那他的參數(shù)應(yīng)該是一個(gè)string,返回值也應(yīng)該是一個(gè)string,但是我們?cè)谡{(diào)用這個(gè)函數(shù)逇時(shí)候并沒有任何的類型提示。import { camelCase } from 'lodash';
const res = camelCase('hello typed');
那在這種情況下我們就需要單獨(dú)的類型聲明,這里我們可以使用detar語句來去聲明一下這個(gè)函數(shù)的類型,具體的語法就是declare function 后面跟上函數(shù)簽名, 參數(shù)類型是input,類型是string,返回值也應(yīng)該是stringdeclare function camelCase (input: string ): string;
那有了這樣一個(gè)聲明過后,我們?cè)偃ナ褂眠@個(gè)camelCase函數(shù)。這個(gè)時(shí)候就會(huì)有對(duì)應(yīng)的類型限制了。那這就是所謂的類型聲明,說白了就是一個(gè)成員他在定義的時(shí)候因?yàn)榉N種原因他沒有聲明一個(gè)明確的類型,然后我們?cè)谑褂玫臅r(shí)候我們可以單獨(dú)為他再做出一個(gè)明確的聲明。那這種用法存在的原因呢,就是為了考慮兼容一些普通的js模塊,由于TypeScript的社區(qū)非常強(qiáng)大,目前一些比較常用的npm模塊都已經(jīng)提供了對(duì)應(yīng)的聲明,我們只需要安裝一下它所對(duì)應(yīng)的這個(gè)類型聲明模塊就可以了。lodash的報(bào)錯(cuò)模塊已經(jīng)提示了,告訴我們需要安裝一個(gè)@types/lodash的模塊,那這個(gè)模塊其實(shí)就是我們lodash所對(duì)應(yīng)的類型聲明模塊,我們可以安裝這個(gè)模塊。安裝完成過后,lodash模塊就不會(huì)報(bào)錯(cuò)了。在ts中.d.ts文件都是做類型聲明的文件,除了類型聲明模塊,現(xiàn)在越來越多的模塊已經(jīng)在內(nèi)部繼承了這種類型的聲明文件,很多時(shí)候我們都不需要單獨(dú)安裝這種類型聲明模塊。例如我們這里安裝一個(gè)query-string的模塊,這個(gè)模塊的作用就是用來去解析url當(dāng)中的query-string字符串,那在這個(gè)模塊當(dāng)中他就已經(jīng)包含了類型聲明文件。我們這里直接導(dǎo)入這個(gè)模塊,我們所導(dǎo)入的這個(gè)對(duì)象他就直接會(huì)有類型約束。那以上就是對(duì)TypeScript當(dāng)中所使用第三方模塊類型聲明的介紹。那這里我們?cè)賮砜偨Y(jié)一下,在TypeScript當(dāng)中我們?nèi)ヒ玫谌侥K,如果這個(gè)模塊當(dāng)中不包含所對(duì)應(yīng)的類型聲明文件,那我們就可以嘗試去安裝一個(gè)所對(duì)應(yīng)的類型聲明模塊,那這個(gè)類型聲明模塊一般就是@types/模塊名。那如果也沒有這樣一個(gè)對(duì)應(yīng)的類型聲明模塊,那這種情況下我們就只能自己使用declare語句去聲明所對(duì)應(yīng)的模塊類型,那對(duì)于declare詳細(xì)的語法這里我們不再單獨(dú)介紹了,有需要的話可以單獨(dú)去查詢一下官方文檔。歡迎加我微信(winty230),拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...