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 字长文)

相关文章
相关标签/搜索