Typescript: 函数类型、函数重载及泛型函数
Typescript
中的函数类型
函数类型包含两部分:参数类型和返回值类型,一个完整的函数类型,这两部分都是需要的。
- 参数类型:以参数列表的形式写出参数类型,并为每个参数指定一个名字和类型
let myAdd: (baseValue: number, increment: number) => number =
function(x: number, y: number): number {
return x + y
}
只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否一样,比如这里的x
对应着baseValue
, baseValue
只是为了增加可读性。
- 返回值类型:在函数和返回值类型之前使用
=>
符号,使之清晰明了
返回值类型是函数类型的必要部分,如果函数没有返回任何值,也必须指定返回值类型为 void
而不能留空。
函数的可选参数、默认参数、剩余参数
原则:传递给一个函数的参数个数必须与函数期望的参数个数一致。
function buildName(firstName: string, lastName: string) {
return firstName + ' ' + lastName;
}
let result1 = buildName('Bob') // Error, 参数过少
let result2 = buildName('Bob', 'Adams', 'Sr.'); // Error, 参数过多
let result3 = buildName('Bob', 'Adams'); // OK
1. 可选参数
JavaScript 里,每个参数都是可选的,可传可不传,不传参的时候,它的值就是 undefined
。
TypeScript 里,可以在参数名旁使用 ?
实现可选参数的功能。 比如,我们想让 lastName
是可选的:
function buildName(firstName: string, lastName?: string): string {
if (lastName)
return firstName + ' ' + lastName
else
return firstName
}
可选参数必须跟在必须参数后面。 如果想让 firstName
是可选的,那么就必须调整它们的位置,把 firstName
放在后面。
2. 默认值
function buildName(firstName: string, lastName = 'Smith'): string {
return firstName + ' ' + lastName
}
与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。
如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined
值来获得默认值。
function buildName(firstName = 'Will', lastName: string): string {
return firstName + ' ' + lastName
}
let result1 = buildName('Bob') // Error, 参数过少
let result2 = buildName('Bob', 'Adams', "Sr.") // Error, 参数过多
let result3 = buildName('Bob', 'Adams') // OK, 返回 "Bob Adams"
let result4 = buildName(undefined, 'Adams') // OK, 返回 "Will Adams"
3. 剩余参数
默认参数和可选参数有个共同点:它们表示某一个参数。有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在 JavaScript 里,你可以使用 arguments
来访问所有传入的参数。
在 TypeScript 里,你可以把所有参数收集到一个变量里,然后给它定义一个类型:
function buildName(firstName: string, ...restOfName: string[]): string {
return firstName + ' ' + restOfName.join(' ')
}
let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')
剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个。
编译器创建参数数组,名字是你在省略号...
后面给定的名字,然后在函数体内使用这个数组。
Typescript 中函数的this
类型
学习如何在 JavaScript 里正确使用 this
就好比一场成年礼。
JavaScript里,this
的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。
下面就来回顾下this
的使用。
let deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
createCardPicker: function() {
return function() {
let pickedCard = Math.floor(Math.random() * 52)
let pickedSuit = Math.floor(pickedCard / 13)
return {suit: this.suits[pickedSuit], card: pickedCard % 13}
}
}
}
let cardPicker = deck.createCardPicker()
let pickedCard = cardPicker()
console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)
运行这个程序,会发现它并没有输出而是报错了。 因为 createCardPicker
执行完后返回一个函数,这个函数直接调用时,this
是指向 window
而不是 deck
对象。
为了解决这个问题,可以在函数被返回时就绑好正确的this
,这样的话,无论之后怎么使用它,都会引用绑定的deck
对象。
还有一种方式是使用箭头函数,因为箭头函数的this
继承于父级函数的this
,父级函数的this
指向deck
。
// 第一种方式 var that = this
let deck = {
...
createCardPicker: function() {
let that = this
return function() {
...
return {suit: that.suits[pickedSuit], card: pickedCard % 13}
}
}
}
// 第二种方式 使用箭头函数,箭头函数能保存函数创建时的 `this` 值,而不是调用时的值。
let deck = {
...
createCardPicker: function() {
return () => {
...
return {suit: this.suits[pickedSuit], card: pickedCard % 13}
}
}
}
在上述的例子中 this
的类型为 any
,这在 typescrit 中是不被允许的。那如何让this
也有类型提示呢?
修改的方法是,提供一个显式的 this
参数,它只是个假的参数,它出现在参数列表的最前面。
让我们往例子里添加一些接口Card
和 Deck
,让类型重用能够变得清晰简单些:
interface Card {
suit: string
card: number
}
interface Deck {
suits: string[]
cards: number[]
createCardPicker (this: Deck): () => Card
}
let deck: Deck = {
suits: ['hearts', 'spades', 'clubs', 'diamonds'],
cards: Array(52),
// 函数现在显式指定其被调用方必须是 deck 类型
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52)
let pickedSuit = Math.floor(pickedCard / 13)
return {suit: this.suits[pickedSuit], card: pickedCard % 13}
}
}
}
现在 TypeScript 知道 createCardPicker
期望在某个 Deck
对象上调用。也就是说 this
是 Deck
类型的,而非 any
。
函数重载
函数重载是 TypeScript 的一项非常强大的特性。它最大优点在于它可以提高代码的可读性和健壮性。
通过为一个函数定义多个函数类型,我们可以清晰地表达函数所能处理的不同参数类型和参数数量,使得代码更加易于理解和维护。
此外,函数重载也帮助我们避免一常见的错误,例如传递错误的参数类型或参数数量过多。
在什么情况下应该使用函数重载呢?
函数重载通常适用于处理多个有相似逻辑,但类型和参数数量不同的函数,例如,axios
库中的 axios
函数可以接受多不同类型的参数用于向服务器发送请求。
// 参数是多样的
axios('url', { ... })
axios({...})
通过函数重载,我们可以将这些相似但参数不同的函数逻辑合并在一起,使得代码更加简洁和易于维护。
先从一个简单的例子看看重载的使用:
function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
return x + y;
}
add(1, 2); // 3
add("hello", "world"); // "helloworld"
第一行和第二行是函数签名,声明了函数的输入和输出:
-
add(x: number, y: number): number;
表示接收两个数字,返回一个数字。 -
add(x: string, y: string): string;
表示接收两个字符串,返回一个字符串。
第三行代码块是根据参数类型和返回类型的不同,处理不同输入情况的函数实现。使用参数类型最宽泛的 any
来处理不属于前两个签名的调用情况。
为了让编译器能够选择正确的检查类型,它与 JavaScript 里的处理流程相似,它查找重载列表,尝试使用第一个重载定义,如果匹配的话就使用这个,因此,在定义重载的时候,一定要把最精确的定义放在最前面。
讲完了函数,最后来看看泛型是如何函数紧密结合在一起的。
泛型函数
这里只是简单介绍下泛型在函数上的应用,后面会详细介绍泛型。
下面这段代码,你肯定会奇怪result
的类型是为什么是any
类型? 因为我传了一个字符串类型的参数进去了,函数体直接返回了这个参数,按道理来说result
应该是字符串类型啊。
function echo(arg) {
return arg
}
const result = echo('str')
这是因为类型推断无法进入到函数体里面去推断返回值的类型,所以我们需要有个类型显示的声明返回值的类型,这个类型是由根据传入的参数类型决定的,即如果传入的是一个数字类型,那么返回值就是数字类型。
那这个时候泛型就登场了,看下面这个例子:
function identity<T>(value: T): T {
return value
}
let output = identity<string>('mystring')
// 还可以省略<string>, ts帮我们做了类型推论,推断T是字符串,推荐使用这种方式,这样代码比较精简。
let output1 = identity('string')
此时,output
的类型就字符串类型。
总结
首先介绍了如何定义一个函数的类型,主要包括参数类型和返回值类型。其中,参数可以是可选的,也可以赋一个默认值,对于多个参数可以使用...
收敛到一个数组中,然后对数组进行类型定义。
接着讲了函数的重载,利用函数的重载可以大大提高函数的灵活性和可维护性,特别适合运用在参数类型和参数个数比较灵活的场景中。
最后讲解了泛型在函数中的应用,泛型是一个非常重要的概念,后面的文章会详细分析它的使用。
转载自:https://juejin.cn/post/7246660018275696695