函数的类型

函數申明

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会优先从最前的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。