一文读懂TS的函数重载,方法重载,构造器重载

14,683 阅读5分钟

这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

重载的理解

用于实现不同参数输入并且对应不同参数输出的函数,在前面定义多个重载签名,一个实现签名,一个函数体构造,重载签名主要是精确显示函数的输入输出,实现签名主要是将所有的输入输出类型做一个全量定义,防止TS编译报错,函数体就是整个整个函数实现的全部逻辑。

函数重载

应用例子:一个班级有很多学生,老师要查询学生的用户信息,如果是输入数字就通过排名(id)去匹配,输入的是字符串就通过分数(grades)去匹配,用TS模拟这种情况。 首先定义用户类型

type User={
    id:number,
    name:string,
    age:number,
    grades:number
}

班级用户信息数据

const userList:User[]=[
    {id:1,name:"小明",age:20,grades:'98'},
    {id:2,name:"小明",age:20,grades:'98'},
    {id:3,name:"小明",age:20,grades:'98'},
    {id:4,name:"小明",age:20,grades:'98'}
]

1.普通实现思路

通过联合类型来做输入定义,输出定义。输入number是(id)是唯一的名次,使用find;输入string是(grades)可能有重复的,使用filter。

function getUserInfo(value:number|string):User|User[]{
    if(typeof value==='number'){
        return userList.find(item=>item.id===value)
    }else{
        return userList.filter(item=>item.grades===value)
    }
}

这样定义TS会报一个异常

image.png 原因切到底层find方法源码,发现是find可能返回的undefined类型

image.png 解决方式很简单,返回类型再联合一个undefined即可解决。

缺点:缺点很明确,都是通过联合类型输入输出,明明知道number输入一定返回User,string输入一定返回User[],这样返回不明确输入什么返回什么,很多时候我们看方法的定义的时候就想看输入啥返回啥,更好的判定类型,为了解决这个函数重载出现了

2.函数重载实现思路

TS的函数重载主要分为多个重载签名+实现签名+函数体

简单理解来看实现签名主要是之前普通实现思路的情况,TS编译的时候检查所有类型和函数体中的对比检查是否存在。实现签名和函数体检查通过后,执行函数的时候实际上是某个重载签名+函数体,跳过了实现签名。

function getUserInfo(value:number):User|undefined
function getUserInfo(value:string):User[]
function getUserInfo(value:number|string):User|User[]|undefined{
    if(typeof value==='number'){
        return userList.find(item=>item.id===value)
    }else{
        return userList.filter(item=>item.grades===value)
    }
}

通过鼠标点击实际调用方法getUserInfo(2),command+鼠标右键,他会自动解析跳转到number这个重载签名上面去,很直观的知道如何调用的。

需求变更: 现在查询相同分数太多,老师输入string想添加一个变量count用于控制查询数量。

修改步骤,第二个重载签名和实现签名添加count定义,函数体书写count逻辑,但是TS报出一下错,意思是实现签名传了两个值,第一个重载签名却只有一个值,那么只有在实现签名增加一个count默认值即可。 image.png 具体实现源码如下

function getUserInfo(value:number):User|undefined
function getUserInfo(value:string,count:number):User[]
function getUserInfo(value:number|string,count:number=1):User|User[]|undefined{
    if(typeof value==='number'){
        return userList.find(item=>item.id===value)
    }else{
        return userList.filter(item=>item.grades===value).slice(0,count)
    }
}
getUserInfo('98',3)

方法重载

方法重载和函数重载其实差不多,还是方法重载是放在类里面的 应用例子:简单封装一个数组,使数组更加好用,通过index删除返回index,通过object删除返回object

class ArrayEN {
  constructor(public arr: object[]) {}

  get(Index: number) {
    return this.arr[Index];
  }
  delete(value: number): number;
  delete(value: object): object;
  delete(value: number | object): number | object {
    this.arr = this.arr.filter((item, index) => {
      if (typeof value === "number") {
        return value !== index;
      } else {
        return value !== item;
      }
    });
    return value;
  }
}

构造器重载

先来理解构造器constructor的原理,构造器是没有返回值的,他会隐式返回一个this,这个this会分配给new对象的左边的变量,至此所有的this都是指向的当前正在使用的对象。

构造器重载和函数重载使基本相同,主要区别是:TS 类构造器重载签名和实现签名都不需要管理返回值,TS 构造器是在对象创建出来之后,但是还没有赋值给对象变量之前被执行,一般用来给对象属性赋值。 应用例子:现在要求算一个图片的面积,这个图形可能是传参可以是对象也可能是长宽

interface OJType{
    width?:number,
    height?:number
}
class Graph{
    public width:number;
    public height:number;

    constructor(width?:number,height?:number)
    constructor(side?:OJType)
    constructor(v1:any,v2?:any){
        if(typeof v1==='object'){
            this.width=v1.width;
            this.height=v1.height
        }else{
            this.width=v1;
            this.height=v2;
        }
    }

    getArea(){
        const {width,height}=this;
        return width*height
    }
}

const g=new Graph(10,10);
console.log(g.getArea())

主要实现思路就是通过构造器将类型定义出来,首先是带有长宽对象的类型,这里才用接口的形式定义,然后就是width,heihgt的number类型,最后简单的通过typeod判断对象类型然后进行赋值,综上发现不管是函数重载,方法重载,构造器重载都是异曲同工之妙,只是需要注意使用场景就行,在vue3的源码很多地方都使用到了,所以有很多场景都是可以用到的,后面会继续深挖TS的一些内容.

image.png