轻松阅读底层框架源码——函数重载 & 方法重载的重要性、优势

194 阅读5分钟

函数重载,方法重载的重要性

著名前端流行框架底层都用到了函数重载,例如vue3底层源码就很多处使用到带泛型的函数重载

函数重载或方法重载适用于完成项目中某种相同功能但细节又不同的应用场景,比如吃饭是一个函数,表示一个吃饭功能,但是西方人用手抓,中国人用筷子,印度人用手抓,这些就是细节不同

优势1:结构分明

让代码可读性强,可维护性提升许多,而且代码更漂亮

优势2:各司其职,自动提示方法和属性

每个重载签名函数完成各个功能,输出取值时不用强制转换就能出现自动提示,从而提高开发效率

优势3:更利于功能拓展

实现微信消息发送函数【真实应用场景,一站理解函数重载优势】

真实应用需求: 有一个获取微信消息发送接口消息查找函数,根据传入的参数从数组中查找数据,如果入参为数字,就认为消息id,然后从后端数据源中找对应的id的数据并返回,否则当成类型,返回一些类的全部消息

type MessageType = 'image' | 'audio' | string //消息类型
type Message = {
  id: number
  type: MessageType
  sendmessage: string
}
let message: Message[] = [
  {
    id: 1, type: "image", sendmessage: "红酥手,黄縢酒,满城春色宫墙柳"
  },
  {
    id: 2, type: "audio", sendmessage: "东风恶,欢情薄"
  },
  {
    id: 3, type: "image", sendmessage: "一怀愁绪,几年离索"
  },
  {
    id: 4, type: "image", sendmessage: "错、错、错"
  },
  {
    id: 5, type: "image", sendmessage: "春如旧,人空瘦,泪痕红浥鲛绡透"
  },
  {
    id: 6, type: "image", sendmessage: "桃花落,闲池阁"
  },
  {
    id: 7, type: "audio", sendmessage: "山盟虽在,锦书难托"
  },
  {
    id: 8, type: "audio", sendmessage: "莫、莫、莫"
  }
]
//不用函数重载来实现
//函数结构不分明,可读性差,可维护性差
function getMessage(value: number | MessageType): Message | Message[] | undefined {
  if (typeof value === 'number') {
    return message.find(val => val.id === value)
  } else {
    return message.filter(val => val.type === value)
  }
}

console.log(getMessage(2))//返回一个Message类型对象
console.log(getMessage('audio'))
let msg=getMessage(2)//Ts没有办运行之前根据传递的值来推导方法最终返回的数据类型
//只可以个根据方法定义的类型展现
// console.log(msg.sendmessage)//类型“Message | Message[]”上不存在属性“sendmessage”。类型“Message[]”上不存在属性“sendmessage”。
//报错原因 因为sendmessage属性存在于Message这个对类型上 而getMessage()函数返回的是一个联合类型
//

//实现方法
let trans_msg=<Message>getMessage(2)
console.log(trans_msg.sendmessage)//东风恶,欢情薄

TS函数重载(function signature overload)定义

TS的函数重载比较特殊,和很多其他后端语言的方法重载相比,多了不少规则。学习函数重载,先要了解什么是函数签名,定义如下

函数签名【funciton signature】: 函数签名=函数名称+函数参数+函数参数类型+函数返回值类型四者合成。在TS函数重载中,包含了实现签名和重载签名,实现签名是一种函数签名,重载签名也是一种函数签名

关于函数重载的定义,我们先来看一个很多其他资料提供的不完整且模糊的TS函数重载定义:

不完整函数重载定义: 一组具有相同名字,不同参数列表的和返回值无关的函数(说法正确,但很模糊)

完整的函数重载定义: 包含了一下规则的一组函数就是TS函数重载

  • 规则1: 由一个签名函数+一个或多个重载签名合成

  • 规则2: 但外部调用函数重载定义的函数时,只能调用重载签名,不能调用实现签名,者看似矛盾的规则其实是TS的规定:实现签名下的函数体是给重载签名编写的,实现签名只能在定义是起到统领所有重载签名的作用,在执行调用的时久看不到实现签名了

  • 规则3: 调用重载函数时,会根据传递的参数来判断你调用的是哪一个函数

  • 规则4: 只有一个函数体,只有实现签名匹配了函数体,所有的重载签名都只有签名,没有匹配函数体

  • 规则5: 关于参数类型规则完整版总结如下

    无论重载签名有几个参数,参数类型是何种类型,实现签名都可以是一个无参函数签名;实现签名参数个数课少于重载签名的参数个数,但是实现签名如果准备包含重载签名的某个位置的参数,那实现签名久必须兼容所有重载签名该位置的阐述类型【联合类型或者any或者unknown的一种】

  • 规则6: 关于重载签名和实现签名的返回值类型规则完整总结如下

    必须给重载签名提供返回值类型,TS无法默认推导。

    提供给重载签名的返回值类型不一定为其执行时的真实返回值类型,可以为重载签名提供真实返回值类型,也可以提供void或者unknown或者any类型,如果重载签名的返回值类型是void或者unknown或者any类型,那么将有实现签名来决定重载签名执行时的真实返回值类型,当然为了明确调用时能有自动提示+可读性更好+避免可能出现了类型强制转换,强烈建议为重载签名提供真实返回值类型

    不管重载签名返回值类型是何种类型【包括泛型类型】,实现签名都可以返回any或者unknown类型,当然一般我们两者都不选择,让TS默认为实现签名自动推导返回值类型

//函数重载来实现
function getMessage(value: number): Message//根据id来查询消息的重载签名
function getMessages(value: MessageType, count: number): Message[]/根据type类型来查询的重载签名
function getMessage(value: any, count: any): Message | Message[] | undefined { 
  if (typeof value === 'number') {
    return message.find(val => val.id === value)
  } else { 
    return message.filter(val => val.type === value).splice(0,count)
  }
}
let msg = getMessage(1)
console.log(msg.sendmessage)//"红酥手,黄縢酒,满城春色宫墙柳"
let msgslist = getMessages('image', 3)
msgslist.forEach(val => { 
  console.log('sendmessage',val.sendmessage)
  // "红酥手,黄縢酒,满城春色宫墙柳"
  // "一怀愁绪,几年离索"
  //"错、错、错"
})