深入浅出TypeScript(3)- 函数重载和泛型
面向对象特性中,最根本的就是面向对象的三大基本特征:封装、继承、多态。同时,TypeScript中也存在多态的使用,比如函数重载,今天我们先看一下函数重载以及泛型的概念。
什么是函数重载
简单来说,函数重载具有两个特征:名称相同,参数不同(参数类型、个数不同。)所以,函数重载的解释应该是具备不同参数的同名函数。注意:函数重载是多态的一种体现。
函数重载的声明和实现
TypeScript中,函数重载主要包括两部分:函数声明,和函数实现。函数声明主要是TSC解析的一种声明体现,实际编译中,并不会编译成具体代码。我们可以通过TypeScript的playground来查看。
1、参数不同的函数重载
加入我们有一个打印函数,可以打印输入的一个string信息,我们可以将函数声明如下:
1 2 | // 函数声明 function print(info: string): void; |
而还有另一种情况,就是输入的有可能是两个string类型的参数,我们都需要打印下来,于是我们的函数声明可以是这样:
1 2 | // 函数声明 function print(info: string, message: string): void; |
而当这两种声明,同时存在TypeScript的声明文件中,我们就需要用函数重载来实现,这是JavaScript没有的特性。
而实现函数重载的要求就是,我们要在一个更为宽泛的范围去实现函数重载,所以,TypeScript中的我们实现print函数如下:
1 2 3 4 5 6 7 8 | // 在更宽泛的范围,我们用可选参数来实现重载 function print(info: string, message ?: string) { let printValue: string = info; if (message){ printValue += message; } console.log(printValue); } |
2、参数个数相同,但类型不同的函数重载
函数重载的第二种情况,参数个数相同,但是参数类型不一样,这种情况下也可以通过重载来实现。
比如,上述打印信息的函数,有可能接受的输入是一个string字符串,也有可能输入接受的是一个number类型的数字,那么我们第一步的函数声明便是如下:
1 2 | function print(info: string): void; function print(num: number): void; |
从上可以看到,我们的函数声明中,参数的类型是不同的,在这种情况下,TypeScript是如何在一个宽泛的范围内实现呢?这里就要用到联合类型,如下所示:
1 2 3 | function print(message: string | number) { console.log(message) } |
函数重载的总结
从我们实现两个函数重载的例子可以看出,我们在TypeScript中实现函数重载的方式分别是利用了TypeScript中的两个类型特性:可选类型以及联合类型。
所以,如果从便捷的角度来讲,我们如果是遇到了类似的实现,其实可以直接使用可选参数和联合类型来实现自己想要的函数效果。
泛型
在函数重载的不同参数类型,相同参数个数的重载中,我们介绍了它的重载实现方式,利用联合类型来实现,但是如果要打印出来的类型有很多,那么我们最终只能用any类型来实现print函数了。
但是,如果用any类型实现一个可以打印任意值的print函数,这样又让我们的函数变得类型缺失,这个时候,泛型这种解决方案也就应运而生。
什么是泛型
泛型指的是一种情况:定义是可以是任意类型,但是在编译的时候,必须有明确的类型。
有点绕,那么我们用泛型来实现上述第二个函数重载的例子,结合这个例子,可以体会一下这句话的含义。
1 2 3 | function print<T>(message: T) { console.log(message); } |
在这个函数中,泛型表示的方式是:函数名称<泛型参数>(arg: 泛型参数)。
这个函数在声明之后,函数类型是一个泛型。我们可以传递任意的类型参数到print函数中,但是当我们传递一个string类型的时候,这个函数便是一个string类型的函数了,已经在tsc编译阶段开始明确指定类型,这是和any函数所不一样的地方。
泛型的好处
首先,我们不用定义过多的联合类型来让函数变得复杂而又冗长,如:
1 2 3 | function print(arg: string | number | boolean | array | 自定义类型) { // 我们应该尽量避免多类型的传值函数,此时我们应该用泛型来实现。 } |
其次,泛型可以是任何类型,但是在编译时一定是类型确定的。而且泛型也可以有继承属性,可以继承接口获取更多的类型定义等。
1 2 3 | function print<T extends Interface> (arg: T) { // 通过继承,来让泛型有更多的可变性。 } |
最后,类型别名也可以是泛型,如我们可以做如下类型定义:
1 | type Person<T> = { age: T } |
泛型总结
总体来说,利用泛型,也是为了更准确的让我们使用类型思维,是为了更准确的描述参数、或者声明的类型准确性,如果能够熟练的掌握泛型,那么在TypeScript的开发中,将会有不一样的体验。
而常常以类型思维去思考JavaScript中的函数或者变量,我们也就会减少很多因为类型方面的犯错,使得我们的项目不仅更好的测试,也会更少的出错。
不得不说,在大前端领域,类型思维的缺失的确是个普遍现象,如果将类型思维捡起来,将会是一个可能存在着痛苦的过程,但是我相信,如果你做到了,那么你不仅会在开发代码的时候会更谨慎,能开发出更优秀的应用程序,还会体验到前端行业别样的魅力。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?