<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你應(yīng)該知道的TypeScript高級(jí)概念

          共 13207字,需瀏覽 27分鐘

           ·

          2020-12-06 10:01


          來源 |?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.age);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); // 子類需要調(diào)用super將參數(shù)傳給父類。        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); // 子類需要調(diào)用super將參數(shù)傳給父類。        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) // 3
          這代代碼編譯不會(huì)出錯(cuò),但是存在一個(gè)顯而易見的缺陷, 就是沒有辦法約束輸出的類型與輸入的類型保持一致。
          這時(shí),我們可以使用泛型來解決這個(gè)問題;
          function identity<T>(arg: T): T {  return arg}
          identity(3) // 3
          我們?cè)诤瘮?shù)名后面加了 , 其中的 T 表示任意輸入的類型, 后面的 T 即表示輸出的類型,且與輸入保持一致。
          當(dāng)然我們也可以在調(diào)用時(shí)手動(dòng)指定輸入與輸出的類型, 如上述函數(shù)指定 string 類型:
          identity<number>(3) // 3
          在泛型函數(shù)內(nèi)部使用類型變量時(shí), 由于事先并不知道它是那種類型, 所以不能隨意操作它的屬性和方法:
          function loggingIdentity<T>(arg: T): T {  console.log(arg.length)   // err   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) // err return arg}
          loggingIdentity({a: 1, length: 1}) // 1loggingIdentity('str') // 3loggingIdentity(6) // err 傳入是參數(shù)中未能包含 length 屬性
          這樣我們就可以通過泛型約束的方法對(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') // successgetProperty(obj, 'm') // err obj 中不存在 m 這個(gè)參數(shù)
          我們可以為泛型中的類型參數(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)該是string
          declare 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ù)人...

          點(diǎn)個(gè)在看支持我吧
          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色护士在线综合资源视频 | 三级片视频在线观看 | 黄色av影视 | 国产第99页 | av天天看 |