TypeScript 知識彙總(二)(3W 字長文)

文章使用的 TypeScript 版本爲3.9.x,後續會根據 TypeScript 官方的更新繼續添加內容,若是有的地方不同多是版本報錯的問題,注意對應版本修改便可。mysql

前言

該文章是筆者在學習 TypeScript 的筆記總結,期間尋求了許多資源,包括 TypeScript 的官方文檔等多方面內容,因爲技術緣由,可能有不少總結錯誤或者不到位的地方,還請諸位及時指正,我會在第一時間做出修改。ajax

文章中許多部分的展現順序並非按照教程順序,只是對於同一類型的內容進行了分類處理,許多特性可能會提早使用,若是遇到不懂的地方能夠先看後面內容。算法

下面內容接 TypeScript 知識彙總(一)(3W 字長文)sql

4.TypeScript 中的函數

4.1 函數聲明

  • 函數聲明法typescript

    function fun1(): string {
      //指定返回類型
      console.log('string')
      return 'string'
    }
    fun1()
    
    function fun2(): void {
      console.log('void')
    }
    fun2()
    複製代碼
  • 函數表達式數據庫

    let fun = function (): number {
      //其實只對通常進行了校驗
      console.log(123)
      return 123
    }
    /* 完整寫法:在後面細講規則 let fun:()=>number = function():number{ console.log(123); return 123; } */
    
    fun()
    複製代碼
  • 匿名函數編程

    let result: number = (function (): number {
      return 10
    })()
    console.log(result) //10
    複製代碼

注意:json

  • TypeScript 中的函數也是能夠作類型校驗的,不過在返回返回值時與箭頭函數返回返回值相似數組

    //(str:string)=>number 就是對於函數的類型校驗,是函數類型Function的深度校驗的寫法
    let fun: (str: string) => number = function (str: string): number {
      console.log(str)
      return 123
    }
    fun('string')
    
    //返回一個具備返回值的函數
    let fun: (str: string) => () => number = function (): number {
      return () => {
        return 123
      }
    }
    /* 可是通常都只寫一邊,由於TypeScript有根據上下文的類型推斷機制,能夠寫更少的代碼 */
    複製代碼
  • TypeScript 中的函數即便沒有返回值也應該顯示聲明返回值爲void類型,不然爲any類型ide

    const a = function():void = {
        console.log("function")
    }
    複製代碼

4.2 函數傳參

在 TypeScript 中對函數傳參,形參也須要指定對應類型,若是實參傳入類型錯誤會報錯,可是形參也能夠指定多種數據類型,也能夠是類

function fun(name: string, age: number): string {
  return `${name}----${age}`
}
console.log(fun('張三', 18))
複製代碼
function fun(name: string, age: number | string): string {
  return `${name}----${age}`
}
console.log(fun('張三', '18'))
複製代碼

4.2.1 可選參數

在 JS 中函數的形參和實參能夠不同,可是在 TypeScript 中必需要一一對應,若是不同就必需要配置可選參數

注意: 可選參數必須配置到參數的最後面,能夠有多個可選參數

//經過給最後的參數加上?來設置可選參數
function fun(name: string, age?: number): string {
  if (age) {
    return `${name}----${age}`
  } else {
    return `${name}----年齡保密`
  }
}
console.log(fun('張三', 18)) //張三----18
console.log(fun('李四')) //李四----年齡保密
複製代碼

4.2.2 默認參數

在 TypeScript 中默認參數的傳入和 JS 中同樣,若是沒有傳入該參數那麼默認就會使用默認參數

注意: 默認參數和可選參數不能在同一個形參變量上使用,默認參數能夠不用寫在參數的最後,可是若是不是寫在最後而是寫在前面的參數上又想要使用默認參數,能夠給可選參數的位置傳入undefined,這樣函數就會使用默認參數

//直接給形參賦值
function fun(name: string = '王五', age?: number): string {
  if (age) {
    return `${name}----${age}`
  } else {
    return `${name}----年齡保密`
  }
}
console.log(fun('張三', 18)) //張三----18
console.log(fun()) //王五----年齡保密
複製代碼
//直接給形參賦值
function fun(name: string = '王五', age: number): string {
  return `${name}----${age}`
}
console.log(fun(undefined, 18)) //王五----18
複製代碼

4.2.3 剩餘參數

在 TypeScript 中的剩餘參數也是和 JS 中的同樣,經過擴展運算符(...)接受剩餘參數

注: 剩餘參數能夠看作是多個可選參數組成的數組

//經過擴展運算符傳入參數
function sum(a: number, b: number, ...result: number[]): number {
  let sum: number = a + b
  sum = result.reduce(function (prev: number, next: number): number {
    return prev + next
  })
  return sum
}

console.log(sum(0, 1, 1, 2, 3, 4, 5)) //16
複製代碼

4.3 函數重載

在 TypeScript 中經過爲同一個函數提供多個函數類型定義來實現多種功能的目的

注:

  • 在 JS 中,若是出現了同名方法,在下面的方法會替換掉上面的方法
  • 在 TypeScript 中的函數重載不一樣於JavaC++這種,而是要在最後一個函數(該函數被叫作函數實體)中經過判斷類型來作到
//函數重載
function fun(name: string): string
function fun(age: number): string
function fun(str: any): any {
  //若是要進行函數重載判斷,那麼這個形參和返回值的類型必需要包含上面函數
  //根據傳入參數的不一樣進入不一樣的重載函數中,雖然這裏的類型爲any,但主要是爲了包含上面,傳參由上面判斷
  if (typeof str === 'string') {
    return '我是' + str
  } else {
    return '個人年齡爲' + str
  }
}
//fun函數能傳入的參數只能是string和number,傳入其餘參數會報錯
console.log(fun('張三')) //我是張三
console.log(fun(18)) //個人年齡爲18
console.log(fun(true)) //錯誤寫法,報錯
複製代碼
//函數重載
function fun(name: string): number //返回不一樣的類型 function fun(name: string, age: number): string function fun(name: any, age?: any): any { //也能夠傳入可選參數,根據參數的不一樣進入不一樣的重載函數 if (age) { return '我是' + name + ',年齡爲' + age } else {
    return 123
  }
}

console.log(fun('張三', 18)) //我是張三,年齡爲18
console.log(fun('張三')) //123
console.log(fun('張三', true)) //錯誤寫法,在重載函數中沒法找到對應函數
複製代碼

4.4 箭頭函數

在 TypeScript 中箭頭也是和 JS 中的同樣的用法

setTimeout((): void => {
  console.log(123)
})
複製代碼

4.5 this

TypeScript 中的 this 是能夠在函數傳參數時手動指定其類型限定,這個類型限定能夠爲 TypeScript 提供推斷依據

class Animal {
  name: string = '動物'
  /* 通常來講能夠直接指定this的指向,在之前的版本若是不知道TypeScript是不知道this應該有什麼屬性的,this顯示爲any類型,只有在編譯時纔會報錯(在新版中已經能夠不用對this進行類型指向了,默認是當前的函數所指向的對象) */
  eat(this: Animal, food: string) {
    // 注意this不會佔據參數的位置,這個函數實際只有一個參數,只用傳入一個參數
    console.log(this.name)
    console.log(food)
    console.log(this.age)
    // TypeScript會報錯,由於Anmial的實例沒有age屬性
  }
}

let a = new Animal()
a.eat('food')
複製代碼
class Animal {
  name: string = '動物'
  eat(this: void) {
    //而若是指定的void再調用下面的則會報錯,由於類型不匹配了
    console.log(this.name)
  }
}

let a = new Animal()
a.eat()
複製代碼

5.TypeScript 中的類

5.1 類的定義

TypeScript 中的類和 JS 中類的定義基本同樣,只是作了額外的類型檢驗

5.1.1 實例類類型

class Person {
  name: string //和JAVA相似,要先在這聲明對象中擁有的屬性和類型
  /*該屬性定義至關於public name:string;,只不過省略了public,下面再作解釋*/
  constructor(n: string) {
    this.name = n //和ES6同樣,傳入構造函數,只不過須要在前面先定義this的屬性
  }

  run(): void {
    console.log(this.name)
  }
}
/* 類也能夠寫成表達式寫法: const Person = class { } */

let p: Person = new Person('張三') //能夠對實例類型變量進行類型的定義,也能夠默認不寫爲any
p.age = 18 //報錯,類只容許有name屬性
p.run() //張三
複製代碼

注意:

  • constructor 函數後面不能加上返回值的修辭符,不然會報錯,能夠看做是固定寫法
  • 函數定義完成後不須要加上符號分割

5.1.2 靜態類類型

上面直接把類做爲類型的修辭符只是用做將該類型限定爲實例的類類型,若是要限定爲類自己須要使用typeof

class Person {
  static max: number = 100
  name: string
  constructor(n: string) {
    this.name = n
  }

  run(): void {
    console.log(this.name)
  }
}

let Person2: typeof Person = Person //把Person類賦值Person2
// 固然這種賦值其實是把Person對象給了Person2
console.log(Person === Person2) // true

//typeof Person是代表該對象是一個Person類的類型,而不是Person的實例類型
Person2.max = 150
// 能夠直接在類上修改原有的屬性,這樣是修改靜態屬性
Person2.min = 0 // 報錯,由於Person類沒有min靜態屬性
let p2: Person = new Person2('李四') //由於Person2也是Person類型的類,因此能夠這樣實例對象
p2.run()
console.log(Person2.max) // 150
複製代碼

5.2 類的繼承

類的繼承和接口不一樣,一個類只能繼承一個父類

class Person {
  name: string
  constructor(n: string) {
    this.name = n
  }
  run(): void {
    console.log(this.name)
  }
}

class Student extends Person {
  //類的繼承能夠說和ES6徹底同樣,只是constructor須要指定類型
  age: number
  constructor(name: string, age: string) {
    super(name)
    this.age = age
  }
  work() {
    console.log(this, age)
  }
}
let s = new Student('李四', 18)
s.run()
s.work() //18
複製代碼

注意: 若是子類裏的方法和父類方法名一致,那麼在使用的時候會使用子類的方法,不會使用父類的方法了

class Person {
  name: string
  constructor(n: string) {
    this.name = n
  }
  run(): void {
    console.log(this.name)
  }
}

class Student extends Person {
  constructor(name: string) {
    super(name)
  }
  run() {
    super.run() //李四,super能夠在子類中表明父類
    console.log(this.name + '子類方法')
  }
}
let s = new Student('李四')
s.run() //李四子類方法
複製代碼

5.3 類的修辭符

在 TypeScript 中定義屬性或方法的時候爲咱們提供了四種修辭符

  • public: 公有類型,在類、子類、類外部均可以對其訪問

    注: 這裏的在類外部訪問就是在實例化事後能在類的外界單獨打印該屬性,而不是隻在內部的方法中使用該屬性

  • protected: 保護類型,在類、子類裏能夠對其進行訪問,可是在類的外部沒法進行訪問

  • private: 私有類型,在類裏面能夠訪問,在子類和類的外部都沒法訪問,在 JS 中要使用私有屬性通常只有用_屬性/方法模塊外部定義內部使用Symbol定義屬性的方法來使用,而在 TypeScript 中更加簡便

    • TypeScript 使用的是結構性類型系統,當咱們比較兩種不一樣的類型時,並不在意它們從何處而來,若是全部成員的類型都是兼容的,咱們就認爲它們的類型是兼容的
    • 當咱們比較帶有 privateprotected成員的類型的時候,若是其中一個類型裏包含一個private成員,那麼只有當另一個類型中也存在這樣一個 private成員,而且它們都是來自同一處聲明時,咱們才認爲這兩個類型是兼容的。對於 protected成員也使用這個規則
    class Animal {
      private name: string
      constructor(theName: string) {
        this.name = theName
      }
    }
    
    class Rhino extends Animal {
      constructor() {
        super('Rhino')
      }
    }
    
    class Employee {
      private name: string
      constructor(theName: string) {
        this.name = theName
      }
    }
    
    let animal = new Animal('Goat')
    let rhino = new Rhino()
    let employee = new Employee('Bob')
    
    animal = rhino //可以賦值,由於二者兼容,都是同一個私有屬性name
    animal = employee // 錯誤: Animal 與 Employee 不兼容,name的私有屬性不兼容
    複製代碼
  • readonly: 只讀類型,可使用 readonly關鍵字將屬性設置爲只讀的, 只讀屬性必須在聲明時或構造函數裏被初始化。同時readonly修辭符是能夠和其餘三個修辭符一塊兒存在的,注意readonly必需要放在第二個位置,只寫readonly默認在前面加了public

    class Octopus {
      readonly name: string
      readonly numberOfLegs: number = 8
      constructor(theName: string) {
        this.name = theName
      }
    }
    
    let dad = new Octopus('Man with the 8 strong legs')
    dad.name = 'Man with the 3-piece suit' // 錯誤! name 是隻讀的.
    複製代碼

注意:

  • 若是屬性不添加修飾符,默認爲公有屬性(public)

    //public
    class Person {
      public name: string
      constructor(n: string) {
        this.name = n
      }
      public run(): void {
        console.log(this.name)
      }
    }
    
    class Student extends Person {
      constructor(name: string) {
        super(name)
      }
    }
    let s = new Student('李四')
    console.log(s.name) //李四
    s.run() //李四
    複製代碼
    //protected
    class Person {
      protected name: string
      constructor(n: string) {
        this.name = n
      }
      public run(): void {
        //若是這個方法是protected下面的s.sun()也會報錯
        console.log(this.name)
      }
    }
    
    class Student extends Person {
      constructor(name: string) {
        super(name)
      }
    }
    let s = new Student('李四')
    console.log(s.name) //報錯
    s.run() //李四
    複製代碼
  • 若是構造函數也能夠被標記成 protected, 意味着這個類不能在包含它的類外被實例化,可是能被繼承

    class Person {
      protected name: string
      protected constructor(theName: string) {
        this.name = theName
      }
    }
    // Employee 可以繼承 Person
    class Employee extends Person {
      private department: string
    
      constructor(name: string, department: string) {
        super(name)
        this.department = department
      }
    
      public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`
      }
    }
    let howard = new Employee('Howard', 'Sales')
    let john = new Person('John') // 錯誤:由於'Person' 的構造函數是被保護的.
    複製代碼
    //private
    class Person {
      private name: string
      constructor(n: string) {
        this.name = n
      }
      run(): void {
        console.log(this.name)
      }
    }
    
    class Student extends Person {
      constructor(name: string) {
        super(name)
      }
      work(): void {
        console.log(this.name)
      }
    }
    let s = new Student('李四')
    console.log(s.name) //報錯
    s.work() //報錯
    s.run() //李四,由於run方法是Person內部的,可使用私有屬性
    複製代碼
  • 在子類中經過super調用父類原型的屬性和方法時也只可以訪問到父類的publicprotected方法,不然會報錯

5.3.1 參數屬性

參數屬性經過給構造函數參數前面添加一個訪問限定符來聲明。 使用 private限定一個參數屬性會聲明並初始化一個私有成員,對於 publicprotectedreadonly來講也是同樣

總的來講,這種寫法是上面先聲明又賦值屬性的簡便寫法,能夠直接經過這種寫法改寫上方先先在前面聲明屬性的寫法,構造函數中也能夠什麼都不寫

  • 聲明瞭一個構造函數參數及其類型
  • 聲明瞭一個同名的公共屬性
  • 當咱們 new 出該類的一個實例時,把該屬性初始化爲相應的參數值
class Octopus {
  readonly numberOfLegs: number = 8
  constructor(readonly name: string) {
    //經過這種寫法改變上面對應readonly的例子
  }
}
複製代碼

5.3.2 可選屬性

與函數的可選參數同樣,在類中也能夠定義類的可選屬性

class Person {
  name?: string
  constructor(n?: string) {
    this.name = n
  }
  run(): void {
    console.log(this.name)
  }
}
/* 等同下面的寫法 */
class Person {
  name: string | undefined
  constructor(n?: string) {
    this.name = n
  }
  run(): void {
    console.log(this.name)
  }
}
複製代碼

5.4 寄存器

TypeScript 中也能夠對一個屬性時用 get 和 set 方法對一個屬性內部的獲取和賦值進行攔截

let passcode = 'secret passcode'

class Employee {
  private _fullName: string
  get fullName(): string {
    //對fullName屬性進行攔截
    return this._fullName
  }
  set fullName(newName: string) {
    if (passcode && passcode == 'secret passcode') {
      this._fullName = newName
    } else {
      console.log('Error: Unauthorized update of employee!')
    }
  }
}

let employee = new Employee()
employee.fullName = 'Bob Smith'
if (employee.fullName) {
  alert(employee.fullName)
}
複製代碼

注意:只帶有 get不帶有 set的存取器自動被推斷爲readonly類型的屬性

5.5 靜態方法和屬性

class Person {
  public name: string
  constructor(n: string) {
    this.name = n
  }
  run(): void {
    console.log(this.name)
  }
}

class Student extends Person {
  static name1: string //設置靜態屬性
  constructor(name: string) {
    super(name)
    Student.name1 = this.name //賦值
  }
  static work(): void {
    //靜態方法
    console.log(Student.name1)
  }
  work(): void {
    console.log(Student.name1)
  }
}
let s = new Student('李四')
console.log(Student.name1) //李四
Student.work()
s.work() //李四
複製代碼

5.6 抽象類

TypeScript 中的抽象類是提供其餘類繼承的基類,不能直接被實例化,只能被其餘類所繼承

abstract關鍵字定義抽象類和抽象類中的抽象方法或屬性,抽象類中的抽象方法不包含具體實現,可是必需要在派生類,也就是繼承的類中實現抽象方法,抽象屬性不須要賦值.而且繼承的類不可以擴展本身的方法和屬性

總的來講,抽象類和抽象方法只是用來定義一個標準,而在其子類在必需要實現這個標準,而且不能擴展抽象類中沒有的標準,不然會報錯

注意:abstract聲明的抽象方法只能放在抽象類中,不然會報錯

abstract class Department {
  abstract age: number
  constructor(public name: string) {
    //參數屬性的一個應用
    this.name = name
  }

  printName(): void {
    console.log('Department name: ' + this.name)
  }

  abstract printMeeting(): void // 必須在派生類中實現
}

class AccountingDepartment extends Department {
  public age: number = 18
  constructor() {
    super('Accounting and Auditing') // 在派生類的構造函數中必須調用 super()
  }
  printMeeting(): void {
    console.log('The Accounting Department meets each Monday at 10am.')
  }
  generateReports(): void {
    console.log('Generating accounting reports...')
  }
}

let department: Department // 容許建立一個對抽象類型的引用
department = new Department() // 錯誤: 不能建立一個抽象類的實例
department = new AccountingDepartment() // 容許對一個抽象子類進行實例化和賦值
console.log(department.age) //18
department.printName()
department.printMeeting()
/* 錯誤: 方法在聲明的抽象類中不存在(由於department是抽象類型,若是是直接寫的AccountingDepartment類型是不會報錯的) */
department.generateReports()
複製代碼

6.TypeScript 中的接口

接口是在面向對象編程中一種規範的定義,它定義了行爲和動做的規範,起一種限制的做用,只限制傳入到接口的數據

TypeScript 中的接口相似於 JAVA,同時還增長了更靈活的接口類型,包括屬性、函數、可索引和類等

注意: 不要把接口看作是一個對象字面量,而更像是一個代碼塊,在其中每一個人屬性或方法的限制能夠用逗號、分號甚至是直接用換行(不寫分號逗號,可是必需要隔行書寫)隔開,若是寫在一行就必須用逗號或分號隔開

6.1 屬性類型接口

  • 屬性類接口通常用做對於 json 對象的約束(下面的代碼尚未使用接口)

    //ts定義方法中傳入參數就是一種接口
    function print1(str: string): void {
      console.log(str) //約束只能且必須傳入一個字符串參數
    }
    print1('string')
    
    /* 對json對象進行約束,這是用了帶有調用簽名的對象字面量,其實仔細一看就像是匿名接口 */
    function print2(obj: { name: string; age: number }): void {
      console.log(obj) //約束只能傳有帶有name和age屬性的對象
    }
    print2({ name: '張三', age: 18 })
    
    function print3(obj: { name: string; age: 18 }): void {
      console.log(obj) //約束只能傳有帶有name和age屬性的對象,而且age必須爲18
    }
    print3('張三', 19) //報錯
    print3('張三', 18)
    複製代碼
  • 對批量方法進行約束:使用接口

    經過interface關鍵詞對接口進行定義

    interface FullName {
      firstName: string //注意這裏要;
      secondName: string
    }
    /* 加入一個用法 let a: FullName['firstName'];//顯示a爲string類型,由於接口中的值能夠單獨獲取來獲得類型 */
    
    function printName(name: FullName): void {
      console.log(name.firstName, name.secondName)
    }
    let obj = {
      //屬性的位置能夠不同
      firstName: '張',
      secondName: '三'
    }
    printName(obj) //傳入對象必須有firstName和secondName
    
    let obj2 = {
      firstName: '李',
      secondName: '四',
      age: 18
    }
    function printInfo(info: FullName): void {
      //使用接口能夠對批量的函數進行約束,而且內部職能
      console.log(info.firstName + info.secondName + info.age)
    }
    // 使用這種方式TypeScript不會進行類型檢驗
    printInfo(obj2) //原則上只能傳入只含有firstName和secondName的對象,可是若是寫在外面傳入也不會報錯
    /* 可是上面這種方法在傳入參數的時候不會報錯,可是在函數內部使用info.age的時候就會報錯,由於接口內部沒有設置age屬性,若是不想報錯,函數內部使用形參的屬性必須是隻能有接口定義的 */
    printInfo({
      firstName: '李',
      secondName: '四',
      age: 18
    }) //經過這種方式傳入就會直接報錯
    複製代碼

    可選屬性接口: 和函數的傳參同樣,能夠在接口處用?表明可選接口屬性

    interface FullName {
      firstName: string //注意這裏要;結束
      secondName?: string
    }
    function printName(name: FullName): void {
      console.log(name.firstName, name.secondName) //張 undefined
    }
    
    printName({
      firstName: '張'
    })
    複製代碼

    案例: 利用 TS 封裝 ajax 請求

    interface Config {
      type: string
      url: string
      data?: string
      dataType?: string
    }
    
    function ajax(config: Config) {
      let xhr: XMLHttpRequest = new XMLHttpRequest()
      xhr.open(config.type, config.url, true)
      xhr.send(config.data)
    
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 20) {
          if (config.dataType.toLowerCase() === 'json') {
            console.log(JSON.parse(xhr.responseText))
          } else {
            console.log(xhr.responseText)
          }
        }
      }
    }
    //因爲接口的要求,必須傳入type和url
    ajax({
      type: 'get',
      data: 'name=張三',
      url: 'http://www.baidu.com/',
      dataType: 'json'
    })
    複製代碼

6.2 函數類型接口

函數類型接口用於對方法傳入的參數和返回值進行約束,可經過接口進行批量約束

//加密的函數類型接口
interface encrypt {
  (key: string, value: string): string
  a: string
}

let md5: encrypt = function (key: string, value: string): string {
  //函數必須是兩個參數,而且類型對應接口,同時返回值必須是接口的返回值string
  return key + '---' + value
}

console.log(md5('name', '張三'))
複製代碼

注: 函數類接口和類類接口類型區別在於函數類接口不用寫函數名,只須要如今後面的參數返回值等,而類類型接口須要限制方法名和屬性等

6.3 可索引類型接口

可索引接口一般用做對數組和對象進行約束(可是這個接口不經常使用)

//對數組使用
interface Arr {
  [index: number]: string //定義索引必須爲number,值爲string,不然會報錯
}
let arr: Arr = ['123', '456']
/* 其實該接口的用法同數組指定類型的定義 let arr:number[]=[1,2,3] let arr:Array<string>=["123","456"] */
//對對象使用,想要約束對象的屬性值時可使用
interface Obj {
  [index: string]: string //定義索引必須爲string,值爲string,不然會報錯
}
let obj: Obj = { name: '張三', age: '20' } //age不能是number
複製代碼
//可索引接口也能夠用來對一個屬性接口進行額外對象的屬性檢驗,能夠用這種方式來跳過屬性檢查
interface Arr {
  //該接口能夠限制一個對象必須有color和width,還能夠有其餘的屬性
  color: string
  width: number
  [propName: string]: any
}
複製代碼

注意: 在同時使用stringnumber類型的可索引接口時,number 類型的索引對應的值必須是 string 類型的子類型或同級類型, 不然會報類型出錯誤

class Animal {
  name: string
  constructor(n: string) {
    this.name = n
  }
}

class Dog extends Animal {
  breed: string
  constructor(n: string, b: string) {
    super(n)
    this.breed = b
  }
}

class Cat extends Animal {
  breed: string
  constructor(n: string, b: string) {
    super(n)
    this.breed = b
  }
}

class Cat extends Animal {
  breed: string
  constructor(n: string, b: string) {
    super(n)
    this.breed = b
  }
}

interface NotOkay {
  [x: number]: Animal // 在這會報錯,數字索引類型「Animal」不能賦給字符串索引類型「Dog」
  [x: string]: Dog
}
/* 實際上二者都存在數字索引最後是被轉換爲了string類型的,好比傳入1實際上時'1',至關於將Animal類型的值轉換爲了Dog,而Dog是Animal類型的子類型,固然不可以父類型轉換爲子類型,而若是數字索引爲其餘的string、number等基礎類型同樣會報錯,由於不是Dog的子類型 */
// 下面兩種都不會報錯
interface NotOkay {
  [x: number]: Cat // 這裏不會報錯是由於Cat擁有Dog相同的方法
  [x: string]: Dog
}
/* interface NotOkay { [x: number]: Bird // 報錯,沒有breed方法 [x: string]: Dog } */

interface NotOkay {
  [x: number]: Dog
  [x: string]: Animal
}
複製代碼

6.4 類類型接口

6.4.1 實例類接口

實例類類型接口主要用於對類的約束,和抽象類類似

注意: 使用實例類接口只會對實例的屬性進行限制,不過對類的靜態屬性進行限制(包括構造器函數 constructor,即便寫了想對應的限制屬性也不會起到做用,要限制須要使用構造器類接口)

interface Animal {
  //其實這個說是屬性類型也沒錯.由於eat也能夠說是一個屬性
  name: string
  eat(str: string): void //這個接口也能夠用做對生成的類的實例化對象的檢驗
}

class Dog implements Animal {
  //類類型接口經過這種寫法限制類
  constructor(public name: string) {} //類中必須有name屬性和eat方法
  eat(str: string) {
    console.log(this.name + '吃' + str)
  }
}
let dog: Dog = new Dog('狗')
dog.eat('狗糧')

class Cat implements Animal {
  private age: number = 2 //也能夠有其餘的屬性,這點和抽象類相同
  constructor(public name: string) {}
  eat() {
    /* 若是接口要字符串類型的參數,這裏能夠不傳參能夠傳字符串類型的參數,若是接口要求不傳參,這裏就不能傳 參,不然報錯 */
    console.log(this.name + '吃魚')
    return 123 //接口爲void或者any或者number時能夠返回number,不然會報錯,其他類型對應
  }
  public showAge() {
    //也能夠有其餘的方法
    console.log(this.age)
  }
}

let cat: Cat = new Cat('貓')
console.log(cat.eat()) //123
cat.showAge() //2
複製代碼

6.4.2 構造器與靜態類接口

實例類接口類型主要是對於類返回的實例進行限制,而構造器類接口就是對類使用new時來對構造器函數進行限制

interface AnimalBehavior {
  eat(str: string): void
}
// 限定一個類有一個構造器接收name與age同時返回的實例對象符合AnimalBehavior接口
interface Animal {
  new (name: string, age: number): AnimalBehavior
  a: string // a就是一個靜態的屬性,也就是函數上的屬性
}
// 這裏的ctor必須有constructor方法而且返回一個AnimalBehavior實例且還有一個靜態的a屬性
function createAnimal(ctor: Animal, name: string, age: number): AnimalBehavior {
  // 這邊的return其實已是由最後返回值得AnimalBehavior來進行限制的,new所作的工做已經結束了
  return new ctor(name, age)
}

class Dog implements AnimalBehavior {
  constructor(name: string, age: number) {}
  static a = 'A' // 必需要有這個靜態的屬性,不然下面的createAnimal函數會報錯
  eat(str: string) {
    console.log('eat ' + str)
  }
}

let d = createAnimal(Dog, 'dog', 2)
d.eat('meat')
複製代碼

6.5 混合類型接口

混合類型接口是講多種類型接口混合從而合成一個集成多種條件限制的接口

//如將函數類型與屬性類型混用,建立一個含有屬性的函數
interface Counter {
  (start: number): number
  interval: number
  reset(): void
}

function getCounter(): Counter {
  let counter: Counter = function (start: number): number {
    return start++
  } as Counter //必須進行斷言,將這個函數當作一個Couter類型,不然會報錯
  counter.interval = 123

  counter.reset = function () {
    this.interval = 0
  }
  return counter
}

let c = getCounter()
//這個混合類型限制的變量自己是個函數,可是有reset方法和interval屬性
c(10)
c.reset()
console.log(c.interval)
c.interval = 5
console.log(c.interval)
複製代碼

6.6 接口擴展

接口擴展與類的繼承相似,能夠用子接口擴展父接口,從而拿到多個接口的限制條件

interface Animal {
  eat(): void
}

interface Person extends Animal {
  //繼承父接口的限制條件
  name: string
  work(): void
}

class Student implements Person {
  //接口會同時將前面二者的接口限制合併
  constructor(public name: string) {}
  eat() {
    console.log(this.name + '吃飯')
  }
  work() {
    console.log(this.name + '上學')
  }
}

let stu: Student = new Student('小明')
stu.eat()
stu.work()
複製代碼
//接口和繼承相結合
interface Animal {
  eat(): void
}
interface Plant {
  wait(): void
}
//也能夠繼承多個接口,用逗號隔開
interface Person extends Animal, Plant {
  name: string
  work(): void
}

class YoungPerson {
  constructor(public name: string) {}
  drink() {
    console.log(this.name + '喝水')
  }
}
//混合繼承和接口限制的類
class Student extends YoungPerson implements Person {
  constructor(name: string) {
    super(name)
  }
  eat() {
    console.log(this.name + '吃飯')
  }
  work() {
    console.log(this.name + '上學')
  }
  wait() {
    console.log(this.name + '停下')
  }
}

let stu: Student = new Student('小明')
stu.eat()
stu.drink()
stu.work()
stu.wait()
複製代碼

6.7 繼承類類型接口

TypeScript 容許類也能夠看成接口來使用,因此也能夠被接口所繼承

class Control {
  private state: any
}

//繼承類的接口能夠繼承到一個類的私有和包含屬性,接口會檢驗一個類是否繼承有該父類的這兩個屬性
interface SelectableControl extends Control {
  select(): void
}
//一個繼承Control類的Button類,雖然state是private類型不能再內部調用.可是確實繼承了這個屬性,不報錯
class Button extends Control implements SelectableControl {
  select(): void {}
}
//只繼承了Control類,內部能夠定義其餘方法
class Radio extends Control {
  select(): void {}
}
//這個類會報錯,由於沒有繼承Control類,沒有state屬性
class Input implements SelectableControl {
  select(): void {}
}
//即便寫了private的state也會報錯,由於state是在上一個類中是私有的,不能在外部訪問,兩個state是不一樣的
class Input2 implements SelectableControl {
  private state = 123
  select(): void {}
}
/* 若是上面的Control類型是public,那麼在Input2中的state只要是設置爲public類型就不會報錯,設置爲其 他類型會和接口不符合,則會報錯 */
複製代碼

7.TypeScript 中的泛型

泛型就是解決類、接口等方法的複用性問題,以及對不特定數據的支持問題的類型

如: 咱們想經過傳入不一樣類型的值而返回對應相似的值,在 TypeScript 中能夠經過 any 類型的返回值解決返回值的不一樣,可是不能解決規定同一個函數傳入指定不一樣類型參數的問題,並且用 any 做爲返回類型性能沒有泛型高,而且不符合規範

7.1 泛型函數

可使用 TypeScript 中的泛型來支持函數傳入不特定的數據類型,要求傳入的參數和返回的參數一致

function fun<T>(value: T): T {
  //通常用T表明泛型,固然也能夠是其餘的非關鍵字和保留字,能夠在函數內用
  let data: T //T就表明着泛型函數要使用的泛型,經過後期的傳入來使用
  data = value
  return data
}

console.log(fun<boolean>(true))
console.log(fun(123))
/* 若是不傳泛型參數會利用類型推論自動推導出來,這裏或推斷出來是number類型,若是沒有指定泛型類型的泛型參數,會把全部泛型參數當成any類型比較 */
複製代碼

注意: 若是編譯器不可以自動地推斷出類型的話,只能像上面那樣明確的傳入 T 的類型,在一些複雜的狀況下,這是可能出現的。在大部分狀況下,都是經過泛型的自動推斷來約束用戶的參數是否正確

7.2 泛型類

經過泛型類能夠實現對類內部不一樣類型變量的分別管理

//如:有個最小堆算法,須要同時支持返回數字和字符串兩種類型,能夠經過類的泛型來實現
class MinNum<T> {
  public list: T[] = []
  add(value: T): void {
    this.list.push(value)
  }
  min(): T {
    let minNum = this.list[0]
    for (let i in this.list) {
      if (minNum > this.list[i]) {
        minNum = this.list[i]
      }
    }
    return minNum
  }
}
let min1 = new MinNum<number>() //經過泛型實現類不一樣變量類型的內部算法,比any類型效率更高
min1.add(1)
min1.add(2)
min1.add(996)
min1.add(7)

console.log(min1.min()) //1

let min2 = new MinNum<string>()
min2.add('a')
min2.add('c')
min2.add('e')
console.log(min2.min()) //a
複製代碼

7.2.1 把類當作參數的泛型類

//將類當作傳參的約束條件,只容許指定的類的實例做爲參數傳入
class Person {
  name: string | undefined
  //這裏若是沒有寫或者爲undefined會報錯,由於TypeScript怕定義了卻不賦值,除非在construct中進行了賦值
  age: number | undefined
}

class Student {
  show(info: Person): boolean {
    //參數只容許傳入Person類的對象
    console.log(info)
    return true
  }
}

let per = new Person()
per.name = '張三'
per.age = 18
let stu = new Student()

stu.show(per)
複製代碼
//使用泛型類能夠手動的對不一樣種類的條件進行約束
//將類當作傳參的約束條件,只容許指定的類的實例做爲參數傳入
class Person {
  name: string | undefined
  age: number | undefined
}

class User {
  userName: string | undefined
  password: string | undefined
}

class Student<T> {
  show(info: T): void {
    //參數只容許傳入Person類的對象
    console.log(info)
  }
}

let per = new Person()
per.name = '張三'
per.age = 18
let stu = new Student<Person>() //T在這傳入的是泛型類,做爲show方法的校驗
stu.show(per)

let user = new User()
user.password = '123456'
user.userName = '張三'

let stu2 = new Student<User>() //能夠寫入不一樣的類
stu2.show(user)
複製代碼

案例

/* 功能:定義一個操做數據庫的庫,支持Mysql,Mysql,MongoDb 要求:Mysql、Mssql、MongoDb功能同樣,都有add、updata、delete、get方法 注意:約束統一的規範、以及代碼重用 解決方案:須要約束規範因此要定義接口,須要代碼重用因此用泛型 */
interface DBI<T> {
  add(info: T): boolean
  update(info: T, id: number): boolean
  delete(info: T): boolean
  get(id: number): any[]
}

//定義一個操做mysql數據庫的類
//注意:要實現泛型接口,這個類應該是個泛型類
class MysqlDb<T> implements DBI<T> {
  add(info: T): boolean {
    console.log(info)
    return true
  }
  update(info: T, id: number): boolean {
    throw new Error('Method not implemented.')
  }
  delete(info: T): boolean {
    throw new Error('Method not implemented.')
  }
  get(id: number): any[] {
    return [
      {
        title: 'xxx',
        desc: 'xxxxx',
        id: id
      },
      {
        title: 'xxx',
        desc: 'xxxxx',
        id: id
      }
    ]
  }
}

//定義一個操做mssql數據庫的類
class MssqlDb<T> implements DBI<T> {
  add(info: T): boolean {
    throw new Error('Method not implemented.')
  }
  update(info: T, id: number): boolean {
    throw new Error('Method not implemented.')
  }
  delete(info: T): boolean {
    throw new Error('Method not implemented.')
  }
  get(id: number): any[] {
    throw new Error('Method not implemented.')
  }
}

//操做用戶表,定義一個User類和數據表作映射
class User {
  username: string | undefined
  password: string | undefined
}

let u = new User()
u.username = '張三'
u.password = '123456'

let oMysql = new MysqlDb<User>() //類做爲約束條件

oMysql.add(u)
console.log(oMysql.get(10))
複製代碼

7.2.2 在泛型裏使用構造器類類型

在 TypeScript 使用泛型建立工廠函數時,須要引用構造函數的類類型

// create函數的參數是一個Class,返回值是這個Class的實例
function create<T>(c: { new (): T }): T {
  return new c()
}
複製代碼

注:c:T的意思是,c 的類型是 T,但這個函數的目的不是要求 c 的類型是 T,而是要求 c 就是 T

// 下面這種做比較
let num = new Number(1)
fn(Number)
fn(num)
複製代碼

更高級的簡寫的用法來使用原型屬性推斷並約束構造函數與類實例的關係

class BeeKeeper {
  hasMask: boolean
}

class ZooKeeper {
  nametag: string
}

class Animal {
  numLegs: number
}

class Bee extends Animal {
  keeper: BeeKeeper
}

class Lion extends Animal {
  keeper: ZooKeeper
}

function createInstance<A extends Animal>(c: new () => A): A {
  return new c()
}

createInstance(Lion).keeper.nametag // typechecks!
createInstance(Bee).keeper.hasMask // typechecks!
複製代碼

解析:

  • c:{new():T}裏的new是構造函數的方法,意思是這個 c 是一個有着構造函數方法的對象,下面的return new c();裏的new是建立一個新的實例的new 兩者是不一樣的東西

  • c:new()=>Tc:{new():T}是同樣的,前者是後者的簡寫,意即 c 的類型是對象類型且這個對象包含返回類型是 T 的構造函數

    注意: 這裏的=>不是箭頭函數,只是用來標明函數返回類型

7.3 泛型接口

經過對接口使用泛型,經過對函數和類接口的使用來本身實現對於傳入參數調節的限制

//由於類和函數接口差距不大,因此這裏就只寫函數類泛型接口
//第一種寫法
interface encrypt {
  <T>(value: T): T
}

let md5: encrypt = function <T>(value: T): T {
  //經過泛型函數賦值
  return value
}

console.log(md5<string>('張三')) //泛型聲明恰好和接口內部的順序相呼應
console.log(md5<boolean>(true))

//第二種寫法
interface encrypt<T> {
  (value: T): T
}
//在將接口給變量的時候就指定類型給
let md5: encrypt<string> = function <T>(value: T): T {
  //經過泛型函數賦值
  return value
}
//在這就能夠直接使用函數,而不須要指定泛型
console.log(md5('張三'))

/* 其實兩種方法根據對於接口<T>寫的位置的不一樣能夠大體推斷出其泛型聲明指定的位置,通常來講第二種在工業編程中用的是最多的 */
複製代碼

7.4 泛型限定

由於泛型能夠是任意的類型,而若是想要對泛型的類型進行相應的約束時,可使用使用extends關鍵字對其進行約束

注意: 這裏的extends再也不是繼承這類意思,並且起到限定與約束做用

function identity<T>(arg: T): T {
  console.log(arg.length) // 這裏會報錯,由於T是任意類型,全部不必定有length屬性
  return arg
}
複製代碼
// 寫成這樣是不會報錯的,由於參數爲一個泛型組成的數組
function identity<T>(arg: T[]): T[] {
  console.log(arg.length) // 這裏會報錯,由於T是任意類型,全部不必定有length屬性
  return arg
}
複製代碼
// 咱們能夠對泛型進行限定來解決報錯
interface Lengthwise {
  length: number
}
// 約束傳入的參數必需要帶有length屬性
function identity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)
  return arg
}
identity(5) // 報錯,5是number類型,不具備length屬性
identity('string') // 字符串具備length屬性
複製代碼

7.4.1 在泛型約束中使用類型參數

能夠聲明一個類型參數,且它被另外一個類型參數所約束。 好比,如今咱們想要用屬性名從對象裏獲取這個屬性。 而且咱們想要確保這個屬性存在於對象 obj上,所以咱們須要在這兩個類型之間使用約束

// 讓K被約束爲T的key,keyof是索引類型查詢操做符
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}

let x = { a: 1, b: 2, c: 3, d: 4 }

getProperty(x, 'a') // okay
getProperty(x, 'm') // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
複製代碼

注: 上面的這種寫法就不能再調用函數的時候來手動寫一下約束條件了,只能讓它自動推斷出來

更多內容

TypeScript 知識彙總(一)(3W 字長文)

TypeScript 知識彙總(二)(3W 字長文)

TypeScript 知識彙總(三)(3W 字長文)

相關文章
相關標籤/搜索