函数的类型
函數申明
在 JavaScript
中,有两种常见的什么方式————函数声明(Function Declarartion)和函数表达式(Function Expression)。
// 函数声明
function sum(x,y){
return x+y
}
// 函数表达式
let sum = function (x,y){
return x+y
}
一个函数有输入和输出,要在 TypeScript
中对其进行约束,需要把输入和输出都考虑到,其中函数什么的类型定义较为简单:
function sum(x, y) {
return x + y;
}
注意,输入多余的(或许少于要求的)参数,是不被允许的:
function sum(x: number, y: number): number {
return x + y
}
sum(1,2,3) // 应有 2 个参数,但获得 3 个。
sum(1) // 应有 2 个参数,但获得 3 个。
sum(1,'2') // 类型“"2"”的参数不能赋给类型“number”的参数。
函数表达式
如果要我们现在写一个对函数表达式的定义,可能会写成这样:
let sum = function(x:number,y:number):number{
return x+y
}
这是可以通过编译的,但是上面代码,只对右侧的匿名函数进行的类型定义,而等式左边的sum
是通过赋值操作的类型推论而推断出来的,如果我们要手动给sum
条件类型,则应该这样:
let sum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y
}
注意不要混淆了 TypeScript
中的=>
和es6中的=>
。
在 TypeScript
中,=>
用来表示函数的定义,左边是输入类型,需要用括号苦熬起来,右边是输出类型(左边)。
在 ES6 中,=> 叫做箭头函数,应用十分广泛(右边)。
let sum: (x: number, y: number) => number = (x: number, y: number): number => {
return x + y
}
用接口定义函数的形状
我们也已用接口定义一个函数需要符合的形状:
interface SearchFunc {
(souce: string, subString: string): boolean
}
let mySearch: SearchFunc;
mySearch = function (a: string, b: string): boolean {
return a === b
}
采用函数表达式|接口定义函数的方式的时候,需要对等号左侧进行类型限制,可以确保以后对数名称赋值的时候保证参数的个数、参数的类型、返回值类型不变。
可选参数
前面提到,输入的参数个数不相等的情况是不允许的,那么如何定义可选的参数呢?和接口中的可选属性类似,我们用?
表示可选的参数:
function buildName(firstName:string,lastName?:string):string {
if(lastName){
return firstName+" "+lastName
}else{
return firstName
}
}
需要注意的是,可选参数必须接在必选参数后面。换句话说,可选参数不允许出现在必选参数了。
function buildName(firstName?:string,lastName:string):string {
if(lastName){
return firstName+" "+lastName
}else{
return firstName
}
} // 必选参数不能位于可选参数后
参数默认值
在ES6中,我们允许给函数的参数添加默认值,TypeScript
会将添加了默认值的参数识别为可选参数:
function buildName(firstName:string,lastName:string = 'cat'):string {
if(lastName){
return firstName+" "+lastName
}else{
return firstName
}
}
buildName('tom','cat')
buildName('tom')
此时就不受[可选参数必须接在必选参数后面]的限制了:
function buildName(firstName: string = 'tom', lastName: string): string {
if (lastName) {
return firstName + " " + lastName
} else {
return firstName
}
}
buildName('tom', 'cat')
buildName(undefined, 'cat')
buildName('tom') // 应有 2 个参数,但获得 1 个。未提供 "lastName" 的自变量。
这里更准确的描述应该是,添加了默认值的参数,如果在后面,因为有默认值的存在相当于以及传入了参数,所以可以不传,所以有类似如[可选参数]的效果。如果在默认值在必选参数前面,因为有默认的值,所以这个参数是存在的,所以后面可以接必选参数,但是这种情况,前面的参数必须传入参数占位才行。
这个情况可以参考函数的传参情况。
function (a,b,c,d){} // 不可能越过a去传bcd
剩余参数
ES6中,可以使用...rest
的方式获取函数的剩余参数(rest参数)
function push(array,...items){
items.forEach(function(item){
array.push(item)
})
}
let a:any[] = [];
push(a,1,2,3)
事实上,items
是一个数组。所以我们可以用数组的类型来定义它:
function push(array:number[],...items:any[]){
items.forEach(function(item){
array.push(item)
})
}
let a = [];
push(a,1,2,3)
注意,rest
参数只能是最后一个参数。
重载
重载允许一个函数接受不同数量或类型的参数时,作为不同的处理。
比如我需要一个函数reverse
,输入数组的时候,输出反转的数组321
,输入字符串hello
的时候,输出反转的字符串olleh
。
利用联合类型,我们可以这么实现:
function reverse(param: string | number): string | number {
if(typeof param== 'number'){
return Number(param.toString().split('').reverse().join(''));
}
return param.toString().split('').reverse().join('')
}
然而这样有一个缺点,就是不能精确的表达,输入为数字的时候,输出也为数字,输入为字符串的时候,输出也为字符串。
这时,我们可以使用重载定义多个reverse
的函数类型:
function reverse(x:string):string;
function reverse(x:number):number;
function reverse(param: string | number): string | number {
if(typeof param== 'number'){
return Number(param.toString().split('').reverse().join(''));
}
return param.toString().split('').reverse().join('')
}
上例中,我们重复定义了多次函数reverse
,前几次都是函数定义(类型判断),最后一次是函数实现(类型要包含定义类型,但是不做为判断类型的)。
注意,TypeScript
会优先从最前的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。