前言
本文介绍ts中函数的定义方式和函数相关的类型系统。涉及函数的定义、对象调用签名、对象构造签名、泛型函数、可选参数等知识点。本章依旧是对冴羽大佬的文章TypeScript 之 More on Functions的学习产出的学习笔记,如有错误欢迎校正。
一、函数类型表达式
最常用的函数定义,类似于箭头函数。下图红框内的函数定义表示这个函数接受一个string类型的参数,不返回任何值。

如果有多个地方需要定义类似的函数类型,可以使用类型别名来简化类型定义

二、调用签名
js中的函数是一个对象Object类型,每个函数对象都有属性值

在ts中,如果我们把一个参数定义为函数,那么ts会默认这个参数携带上了函数对象固有的属性如 name、length、call、apply等

如果传入的函数还需要携带其他属性怎么办?这时候就需要使用调用签名来给函数定义额外的属性。我们在对象类型上声明一个调用签名,表示这个类型的对象可以作为函数调用,同时给这个对象添加一个des属性。可以看到下图,这个对象不仅可以调用,还多了额外的des属性

三、构造签名
对象类型的定义中不仅可以添加调用签名,还可以添加构造签名,构造前面就是在调用签名加一个new
关键字

有一些对象如Date、Array等不仅可以使用new关键字创建还可以直接调用它本身创建

对于这种对象,我们可以同时添加构造签名和调用签名

四、泛型函数
4.1 理解泛型
泛型可以类比为一个模板,任何一个类型都可以套用这个模板来获取一定的输入输出。
我们来看一下如果没有这个所谓的"模板",要写一个函数,这个函数接收一个数组,返回数组的最后一个元素,怎么实现:
- 使用any
使用any能够快速解决我们的问题,但这样不就成了"anyscript",那还不如写js(狗头)

- 使用重载函数(详情请查看第六章)
这样太罗嗦了,而且不能覆盖到所有的类型,每个新类型都要定义一次重载函数

- 使用泛型
我们定义一个"模板",任何一个类型都可以使用这模板让ts自己推断要返回的类型,可以看到下图,函数的返回值ts都能准确判断出它的类型。原因就是我们定义了一个泛型,这个泛型接收一个类型的数组并返回这个类型的参数。

4.2 类型自动推断
4.1章我们定义的泛型函数使用时的类型是靠ts自动推断出来的,我们也可以显式的声明类型

在一些较复杂的函数中,我们可能需要使用多个类型的参数。下图我们接收一个Map对象,返回这个Map对象的键值对数组

4.3 泛型约束
有时候我们只想操作某些固定字段,使用泛型约束可以对类型进行限制。
如下图,我们要求使用这个函数的类型都必须要有name和age属性字段

现在我们有学生、工人、宠物三个类型,我们传入greet函数看看效果,可以看到拥有age和name字段的学生和工人能够正常使用greet函数,而宠物类型使用时报错了。

五、可选参数
这个比较简单,在类型定义前面加一个问号,表示这个参数可以不传。此时这个参数的类型为string|undefined

需要注意的是,可选参数后面只能继续写可选参数,不能写必传参数。

六、函数重载
在ts中支持函数重载。我的理解是让同一个函数拥有多种调用方式。ts的重载不像java或者c#那种每个重载函数都需要实现一遍,编译器根据传入的参数判断执行逻辑,而是先定义函数的多种调用方式,最后写一个兜底的函数。
下图中,我们定义add的三种调用形式,最后写了一个兜底的add函数进行统一实现

为什么b、c这两个参数在具体实现时变成了可选参数?
这是为了兼容只传入参数a的情况,如果只传入参数a,那么其他值不正是可选的吗?
兜底的函数实现需要考虑所有重载函数的调用情况,如下图。我们希望传入的参数能够返回他们的字符串形式的类型,在编写兜底函数时需要兼容前面三种情况。

七、this声明
js的this是每个初学者的噩梦,this的指向会根据实际情况指向不同的对象。因此在ts中,需要明确告诉ts,这个函数中的this指向谁。
下图中,sayHi函数明确了函数中的this指的是User类型。

给其他的类型对象用会报错

需要注意的是,如果ts认为,如果一个对象它会唱、跳、rap、和篮球那么它就是我家哥哥。可以看到下图,我们让Student类型与User类型的属性保持一致,也可以正常使用sayHi函数。

九、剩余(Rest)参数与展开(Spread)语法
9.1 Rest参数
在js中定义函数可以使用...
放在所有参数后面,表示后面还可以传入多个参数,这就是剩余参数的定义方法。在ts中则要求开发者告知这些剩余参数的类型。

需要注意的是,剩余参数必须定义成数组类型或元组类型。
9.2 Spread语法
使用...
也可以将数组展开,传入函数。比如,要获取数字数组中的最大值,可以使用展开语法
ini
const nums = [2,3,4,1,9,0]
Math.max(...nums)
需要注意的是,ts默认数组是可以改变的,因此如果要把数组展开到已知参数个数的函数,ts会报编译错误:

为了解决这个问题,我们可以使用as const来把数组变为只读元组,或者直接传入一个元组

十、参数解构
函数接受一个对象类型时,可以使用参数解构语法来定义变量
