在学习TypeScript的过程中,我们能清晰感受到它与JavaScript的区别,其中类型定义带来的严谨和规范,能提高代码的阅读性和可维护性。那对于js开发者最常用的函数,又该如何去定义,本文将介绍ts中的函数类型定义。
一、函数类型
1. 函数类型的定义:
在使用TypeScript时,我们创建函数要明确两个点:
- 确定参数的类型
- 确定返回值的类型
通过函数声明创建函数:
function fn (num : number) : void {
console.log(num)
}
我们在调用函数时能得到函数的类型提示,提示我们需要什么类型的参数,返回值是什么类型:
通过函数表达式创建函数:
const fn = function(num : number) : void {
console.log(num)
}
同样,我们先来看下变量fn的类型:
相比前者,函数表达式的写法可以增加阅读体验,例如我们用type关键字,将函数类型提取出来:
type FnType = (num: number) => void
const fn : FnType = (num) => {
console.log(num);
}
那当我们在使用函数‘fn’时,得到的提示应该是类型‘FnType’为主:
将定义好的函数类型规范‘fn’变量,我们在赋值时无需再书写函数参数以及函数返回值的类型,typescript会对赋值内容进行自动推导再将两者对比。
2. 可选参数与默认参数:
- 可选参数:
我们先看一个案例:
function fn(a, b){
// 如果调用fn时有传入b,则进行b的打印
if(b !== 'undefined') console.log(b)
console.log(a)
}
在JavaScript中,我们定义好一个函数的参数是用于哪块逻辑,在调用函数时,如果参数没传入,默认是传undefined。
但在Ts中,函数类型的定义,必须对每个参数的类型进行定义,而面对这种非必选的参数,需要通过"?"符号进行标记:
// 函数声明
function fn(a: number, b?: string) : void {
if (b !== 'undefined') console.log(b)
console.log(a)
}
// 函数表达式
type FnType = (a: number, b?: string) => void
const fn: FnType = (a, b) => {
if (b !== 'undefined') console.log(b)
console.log(a)
}
可选参数必须在必填参数后,否则不符逻辑,会得到对应的错误提示:
- 默认参数:
为什么要将可选参数和默认参数区分开来?主要是在写法上容易混淆,我们先看下函数表达式的写法:
function fn(a: number, b = '1') : void {
if (b !== 'undefined') console.log(b)
console.log(a)
}
接着我们可以看下typescript编译器对fn类型的提示:
在函数声明中,参数给予默认值后无需再写类型,默认值的类型为参数类型,且这个参数为可选参数。
接下来是函数表达式对默认参数的书写,类型的定义书写可选,而函数的定义书写默认值:
type FnType = (a: number, b?: string) => void
const fn: FnType = (a, b = '1') => {
if (b !== 'undefined') console.log(b)
console.log(a)
}
这里不难理解,我们对fn进行类型定义,要求的是第二个参数为可选。
赋值给fn的匿名函数,在进行类型推导时,认定第二个携带默认值的参数为可选参数,符合fn变量类型的要求。
3. 剩余参数的获取:
我们复习一下js中的argument,我们可以通过arguments获取传入的所有参数,其中包括我们没有定义接收的参数:
function fn(a, b){
console.log(arguments);
}
fn(1,2,3,4) // arguments : {'0':1,'1':2,'2':3,'3':4}
在ts中,我们定义函数接收多少个参数后,无法传入更多的参数:
我们可以回忆一下,在ES6中对多余参数的收集:
function fn(a, b, ...rest){
console.log(rest) // [ 3, 4 ]
}
fn(1,2,3,4)
同理,在ts中,我们需要对剩余参数进行类型定义,确定剩余参数的类型。
4. 关于this的类型定义:
在js中,this的指向是一个重要知识点,在ES6之前,普通函数的this指向只能在调用时确定,其中包括this的指向丢失,call、apply、bind对this指向的改变等;在ES6学习箭头函数后,this的指向可以在函数定义时确定,箭头函数的this指向函数所在的执行上下文。
那在严谨的逻辑中,一个函数的定义应该清晰。
- 函数的参数(需要什么参数,参数的数据类型)。
- 函数的作用(返回什么类型的数据,或者是没有返回值)。
- 函数服务的目标(this的指向)。
我们先看一个情景前提:
const example = {
num: 1,
getNum() {
console.log(this.num);
}
}
const fn = example.getNum
fn() //undefined, this => window
这是最常见的一种场景,getNum函数定义的目的是打印example中的num属性,在赋值过程中,this隐式丢失,导致函数调用时指向window。
没错,可能有同学认为,函数的功能既然能打印example的num属性,也可以打印window的num属性,那取出来也没什么问题,甚至我们可以通过call来指向我们想取出num属性的目标:
example.getNum.call(otherTarget) // otherTarget.num
但如果我这个函数,取出的num属性有类型要求,用于后续逻辑,其他目标的num属性类型是否符合我后续逻辑的要求?
const example = {
num: {a : 1},
getNum() {
console.log(this.num.a.b);
}
}
// 错误使用
const otherTarget = {
num : '111'
}
example.getNum.call(otherTarget) // error
所以我们必须明确函数服务的目标,以免误用导致逻辑错误。
那在ts中,如果明确了函数服务的目标,也就是getNum函数服务于example对象,我们可以对this进行类型定义:
interface Example {
num : number,
getNum : (this : Example) => void
}
const example : Example = {
num: 1,
getNum() {
console.log(this.num);
}
}
定义好函数服务的目标后,我们可以再对比同一段逻辑的使用过程:
5. 函数重载(Overloads):
在js中定义一个函数,我们可以通过判断用户传入的类型来决定执行哪一点逻辑:
function fn (target) {
if(typeof target === 'string') return '1'
else return 1
}
fn('1') //return '1'
console.log(fn(1)) // return 1
在ts中,我们需要进行参数的类型定义,还需要确定返回值的定义:
function fn (target: number | string): string | number {
if(typeof target === 'string') return '1'
else return 1
}
但是这种写法得到的返回值会是什么提示呢:
如图所示,我们声明的变量f在接受返回值后的得到的类型提示却是字符串和数值的联合类型,不符合我们设计的目的。
函数重载则符合我们设计函数的书写要求:
function fn (target:number) : number
function fn (target:string) : string
function fn (target: number | string): string | number {
if(typeof target === 'string') return '1'
else return 1
}
重载用于记录不同的组合列表,我们在调用函数时,会去进行列表的匹配,由此确定调用者是使用函数的哪一段逻辑,返回什么类型的数据。
总结
在文章中我们学习函数的类型定义,在创建函数时需要确定函数的参数类型和返回值类型,声明函数和函数表达式如何定义,可选参数和默认参数的定义,剩余参数的定义,this的类型定义以及函数重载。相比js中函数的书写,ts的学习成本相对较高,但是从js过渡到ts的过程又是流畅的。