TypeScript中的方法重载详解


Posted in Javascript onApril 12, 2019

前言

方法重载(overload)在传统的静态类型语言中是很常见的。JavaScript 作为动态语言, 是没有重载这一说的。一是它的参数没有类型的区分,二是对参数个数也没有检查。虽然语言层面无法自动进行重载,但借助其动态的特性,我们可以在代码中手动检查入参的类型,或者通过 arguments 获取到参数个数,从而实现根据不同的入参做不同的操作。

比如有一个获取聊天消息的方法,根据传入的参数从数组中查找数据。如果入参为数字,则认为是 id,然后从数据源中找对应 id 的数据并返回,否则当成类型,返回这一类型的消息。

function getMessage(query) {
 if (typeof query === "nunber") {
 return data.find(message => message.id === query);
 } else {
 return data.filter(message => message.type === query);
 }
}

TypeScript 中,假如我们的消息数据为如下结构:

type MessageType = "string" | "image" | "audio";

type Message = {
 id: number;
 type: MessageType;
 content: string;
};

上面获取数据的方法等价于:

function getMessage(
 query: number | MessageType
): Message[] | Message | undefined {
 if (typeof query === "number") {
 return data.find(message => message.id === query);
 } else {
 return data.filter(message => message.type === query);
 }
}

这样做一是类型书写上比较丑陋,二是没有发挥出 TypeScript 类型检查的优势,这里我们是可以根据入参的类型明确知道返回的类型的,即如果传入的是 id,返回的是单个数据或undefined,如果是根据类型查找,返回的是数组。而现在调用方法后,得到的类型太过宽泛,这和使用 any 做为返回没多大差别。

TypeScript中的方法重载详解

函数返回类型不够紧凑

因为类型的不明朗,返回的结果都不能直接操作,需要进行类型转换后才能继续。

const result1 = getMessage("audio");
/** 不能直接对 result1 调用数组方法 */
console.log((result1 as Message[]).length);

const result2 = getMessage(1);
if (result2) {
 /** 不能对 result2 直接访问消息对象中的属性 */
 console.log((result2 as Message).content);
}

重载的实现

这时候可通过提供多个函数类型的声明来解决上面的问题,最后得到的结果就是间接实现了函数的重载。当然这个重载只是 TypeScript 编译时的。

function getMessage(id: number): Message | undefined;
function getMessage(type: MessageType): Message[];
function getMessage(query: any): any {
 if (typeof query === "number") {
  return data.find(message => message.id === query);
 } else {
  return data.filter(message => message.type === query);
 }
}

这样改造后,我们在调用的时候直接就会有重载的提示。

TypeScript中的方法重载详解

实现 TypeScript 的重载后调用时的自动提示

并且得到的结果类型是重载方法中指定的入参与返回的组合,在对结果进行使用时,无须再进行类型转换。

const result1 = getMessage("audio");
/** ✅ 无须类型转换 */
console.log(result1.length);

const result2 = getMessage(1);
if (result2) {
 /** ✅ 无须类型转换 */
 console.log(result2.content);
}

这里需要理解的是,上面添加的函数类型仅作为 TypeScript 在编译时使用的,它不是真的实现像传统静态类型语言那样的重载,也不会改变编译后代码的输出。实际运行时仍然是不带重载的 JavaScript 版本。

编译后的代码

但这一点也不影响我们在 TypeScript 中使用这种假的重载。

可选参数

另一个 TypeScript 重载的场景。还是上面获取消息数据的方法,因为根据类型查找消息时,会返回同类型消息的一个数组。此时我们想加一个参数实现只返回结果中前几个数据,那么可以很方便地进行如下的改造:

function getMessage(id: number): Message | undefined;
+function getMessage(type: MessageType, count?: number): Message[];
+function getMessage(query: any, count = 10): any {
 if (typeof query === "number") {
  return data.find(message => message.id === query);
 } else {
+  return data.filter(message => message.type === query).splice(0, count);
 }
}

通过重载,这个新增的参数很容易实现只针对入参 MessageType 时,这样如果我们有如下的调用,会得到编译时的报错:

/** ? Argument of type '1' is not assignable to parameter of type 'MessageType' */
getMessage(1,10);

而非重载的版本是享受不到上面提到的类型优势的。

function getMessage(
 query: number | MessageType,
 count = 10
): Message[] | Message | undefined {
 if (typeof query === "number") {
  return data.find(message => message.id === query);
 } else {
  return data.filter(message => message.type === query).splice(0, count);
 }
}

/** ✅ ojbk, 不错报 */
getMessage(1, 10);

重载过程

TypeScript 重载的过程是,拿传入的参数和重载的方法签名列表中由上往下逐个匹配,直到找到一个完全匹配的函数签名,否则报错。所以推荐的做法是将签名更加具体的重载放上面,不那么具体的放后面。

/** ✅*/
function getMessage(type: MessageType, count?: number): Message[];
function getMessage(id: number): Message | undefined;

/** ?*/
function getMessage(id: number): Message | undefined;
function getMessage(type: MessageType, count?: number): Message[];

像上面示例中正确做法这样,如果说入参个数只有一个,那可以直接跳过第一个函数签名,无须做入参类型的判断。

相关资源

  • TypeScript Handbook - Functions - Overloads
  • Typescript method overloading

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JQuery的Validation插件中Remote验证的中文问题
Jul 26 Javascript
JavaScript高级程序设计 DOM学习笔记
Sep 10 Javascript
基于jquery跨浏览器显示的file上传控件
Oct 24 Javascript
让低版本浏览器支持input的placeholder属性(js方法)
Apr 03 Javascript
javascript实现动态模态绑定grid过程代码
Sep 22 Javascript
Javascript基础教程之if条件语句
Jan 18 Javascript
js+div实现文字滚动和图片切换效果代码
Aug 27 Javascript
JS实现获取来自百度,Google,soso,sogou关键词的方法
Dec 21 Javascript
EasyUI中的dataGrid的行内编辑
Jun 22 Javascript
使用Node.js实现一个多人游戏服务器引擎
Mar 13 Javascript
在vue中阻止浏览器后退的实例
Nov 06 Javascript
vue 根据选择的月份动态展示日期对应的星期几
Feb 06 Vue.js
vue-cli 3.x配置跨域代理的实现方法
Apr 12 #Javascript
解决微信小程序调用moveToLocation失效问题【超简单】
Apr 12 #Javascript
详解Bootstrap 学习(一)入门
Apr 12 #Javascript
vue组件中iview的modal组件爬坑问题之modal的显示与否应该是使用v-show
Apr 12 #Javascript
vue+echarts实现可拖动节点的折线图(支持拖动方向和上下限的设置)
Apr 12 #Javascript
详解Vue中使用插槽(slot)、聚类插槽
Apr 12 #Javascript
JS实现li标签的删除
Apr 12 #Javascript
You might like
php实现用于删除整个目录的递归函数
2015/03/16 PHP
php封装好的人民币数值转中文大写类
2015/12/20 PHP
PHP使用PDO 连接与连接管理操作实例分析
2020/04/21 PHP
添加JavaScript重载函数的辅助方法2
2010/07/04 Javascript
JavaScript小技巧 2.5 则
2010/09/12 Javascript
JS中的substring和substr函数的区别说明
2013/05/07 Javascript
JavaScript闭包实例讲解
2014/04/22 Javascript
JScript中的条件注释详解
2015/04/24 Javascript
Javascript中apply、call、bind的巧妙使用
2016/08/18 Javascript
微信小程序 rpx 尺寸单位详细介绍
2016/10/13 Javascript
详解微信小程序 通过控制CSS实现view隐藏与显示
2017/05/24 Javascript
前端跨域的几种解决方式总结(推荐)
2017/08/16 Javascript
详解使用VUE搭建后台管理系统(vue-cli更新至3.0)
2018/08/22 Javascript
javascript中关于类型判断的一些疑惑小结
2018/10/14 Javascript
vue服务端渲染操作简单入门实例分析
2019/08/28 Javascript
JavaScript基于面向对象实现的无缝滚动轮播示例
2020/01/17 Javascript
Python日期的加减等操作的示例
2017/08/15 Python
python操作xlsx文件的包openpyxl实例
2018/05/03 Python
通过python爬虫赚钱的方法
2019/01/29 Python
使用python的pexpect模块,实现远程免密登录的示例
2019/02/14 Python
python 生成器和迭代器的原理解析
2019/10/12 Python
python 读取数据库并绘图的实例
2019/12/03 Python
Python 读取有公式cell的结果内容实例方法
2020/02/17 Python
HTML5 Canvas中使用路径描画二阶、三阶贝塞尔曲线
2015/01/01 HTML / CSS
德国隐形眼镜店:LuckyLens
2018/07/29 全球购物
单位门卫岗位职责
2013/12/20 职场文书
三方股东合作协议书范本
2014/09/28 职场文书
政风行风自查自纠报告
2014/10/21 职场文书
事业单位聘任报告
2015/03/02 职场文书
个人的事迹材料怎么写
2019/04/24 职场文书
2019已经过半,你知道年中工作总结该怎么写吗?
2019/07/03 职场文书
完美解决golang go get私有仓库的问题
2021/05/05 Golang
配置nginx 重定向到系统维护页面
2021/06/08 Servers
Python实战之OpenCV实现猫脸检测
2021/06/26 Python
CSS实现单选折叠菜单功能
2021/11/01 HTML / CSS
Python中的pprint模块
2021/11/27 Python