函数的调用签名与重载

调用签名描述了函数的参数个数、类型及返回值类型,它不仅可以用于表示函数的类型,还可以用于声明重载函数。下面将分别介绍调用签名和重载函数。

调用签名

调用签名表示函数的类型,它能够描述函数的参数及返回值,其语法如下。

let 变量名称:(参数1:类型,参数2:类型,...,参数n:类型) => 返回值类型;

调用签名的参数也可以是可选参数或剩余参数。

例如,在以下代码中,通过调用签名,将变量 func 声明为 (num1: number, num2:number) => number 类型,之后在为变量赋值时,它必须初始化为符合该调用签名的函数。

let func: (num1: number, num2: number) => number;

func = function (num1: number, num2: number): number {
    return num1 + num2
};
let result1 = func(1, 2); //值为3

//如果在调用签名处指明了类型,那么具体函数中可以不用指明类型
//编译时会自动将参数及返回值推导为调用签名的类型
//num1和num2会自动推导为number,返回值也会自动推导为number
func = function (num1, num2) { return num1 - num2 };
let result2 = func(3, 1); //值为2

//参数名称与调用签名中的参数名称不需要保持一致
//只要参数顺序和类型与调用签名保持一致就可以通过编译
func = function (a, b) { return a * b };
let result3 = func(2, 3); //值为6

如果被赋值的函数不符合调用签名,则会引起编译错误,示例代码如下。

let func: (num1: number, num2: number) => number;

//编译错误:不能将类型"(num1: number, num2: number) => string"分配给类型"(num1: number,
//num2: number) => number"
//编译错误:不能将类型"string"分配给类型"number"。ts(2322)
func = function (num1, num2) { return "a"; }

//编译错误:不能将类型"(num1: string, num2: number) => string"分配给类型"(num1: number,
//num2: number) => number"
//参数"num1"和"num1"的类型不兼容
//不能将类型"number"分配给类型"string"。ts(2322)
func = function (num1: string, num2) { return num1 + num2; };

//编译错误:不能将类型"(num1: any, num2: any, num3: any) => any"分配给类型"(num1: number,
//num2: number) => number"。ts(2322)
func = function (num1, num2, num3) { return num1 + num2 + num3 };

注意,如果实际函数的参数类型与调用签名匹配,但参数个数比调用签名少或者定义为可选参数或默认参数,则不会引起编译错误,示例代码如下。

let func: (num1: number, num2: number) => number;

//以下函数不会引起编译错误
func = function (num1) { return num1; };
func = function (num1?, num2?) { return num1 + num2; }
func = function (num1 = 0, num2 = 0) { return num1 + num2; }

重载函数

同时拥有多个调用签名的函数即为重载函数,这些签名拥有不同的参数个数和类型,可以执行不同的代码逻辑。

TypeScript 的重载功能并不完善。在其他语言中,每个重载签名下都可以拥有单独的函数体。在 TypeScript 中,所有重载签名共用一个函数体,需要自行在函数体中用代码分支来判断。

下面分别介绍重载函数的两种声明形式。

  1. 以普通形式声明重载函数

    重载函数的普通声明形式如下。

    function 函数名(参数a1:类型,参数a2:类型,...,参数an:类型):返回值类型;
    function 函数名(参数b1:类型,参数b2:类型,...,参数bn:类型):返回值类型;
    ...
    function 函数名(参数z1:类型,参数z2:类型,...,参数zn:类型):返回值类型 {
        //函数体代码
        // 如果有返回值,则需要写"return 返回值;"语句
    }

    该声明语句分为以下两个部分。

    • 没有函数体的函数声明:它们都是重载签名,当调用该函数时,只能以符合这些签名之一的形式来调用函数。重载签名至少要定义两个,否则以重载方式声明函数没有任何意义,不如直接定义非重载函数。

    • 最后一个拥有函数体的函数声明:它并非重载签名,而是针对所有重载签名的具体实现,它的对应位置的参数类型和返回值类型必须能兼容前面所有重载签名中的类型,否则会引起编译错误。

    例如,以下代码定义了一个 combine() 函数,该函数拥有 3 个重载签名,其参数和返回值分别为布尔类型的值、字符串类型的值、数值类型的值,而该函数的重载实现部分的参数和返回值都定义成 any,以兼容前面所有签名的类型,然后通过类型运算符判断传入的参数值类型,并针对不同类型的值进行处理。

    function combine(a: boolean, b: boolean): boolean;
    function combine(a: string, b: string): string;
    function combine(a: number, b: number): number;
    function combine(a: any, b: any): any {
        if (typeof a == "boolean" && typeof b == "boolean") {
            return a || b;
        }
        else {
            return a + b;
        }
    }

    上述函数拥有 3 个不同类型的参数和返回值的重载签名,因此用对应的 3 种方式调用,示例代码如下。

    let value1 = combine(1,2);        //value1为number类型,值为3
    let value2 = combine("a","b");    //value2为string类型,值为"ab"
    let value3 = combine(true,false); //value3为boolean类型,值为true

    在 Visual Studio Code 中,输入该函数名称,Visual Studio 会列出 3 种允许的调用方式,如图8-4所示。

    image 2024 02 18 13 11 39 235
    Figure 1. 图8-4 3种允许的调用方式

    当调用重载函数时,只能以符合重载签名的方式调用,而不能以具体重载实现函数的方式调用。例如,上述代码中实现的重载函数的参数均为 any,返回值也为 any,但它并非函数签名,并不表示该函数真正支持 any 类型。

    以下代码向函数传入了长整型参数,虽然满足 any 类型,但前面的函数重载签名只支持布尔类型、字符串类型和数值类型,因此调用时会引起编译错误。

    //编译错误:没有与此调用匹配的重载。 ts(2769)
    let value4 = combine(1n,2n);
  2. 以表达式形式声明重载函数

前面介绍的调用签名实际上是简写形式的签名,其语法如下。

let 变量名称:(参数1:类型,参数2:类型,...,参数n:类型) => 返回值类型;

将它写为完整形式,语法如下。这种形式的写法和 8.1 节中的写法是完全等效的,只是写法不同。

let 变量名称:{
    (参数1:类型,参数2:类型,...,参数n:类型) : 返回值类型
};

何时应当使用简写形式的调用签名?何时应当使用完整形式的调用签名?如果函数只有一个调用签名,则建议使用简写形式;如果函数需要重载,就需要使用完整形式的调用签名。

前面的示例函数 combine() 也可以以表达式的形式声明,代码如下。

//以下为调用签名部分
let combine: {
    (a: boolean, b: boolean): boolean
    (a: string, b: string): string
    (a: number, b: number): number
};

//以下为具体实现函数部分
//实现函数的参数与返回值类型必须兼容所有签名中对应位置的类型,否则会引起编译错误
combine =
    function (a: any, b: any): any {
        if (typeof a == "boolean" && typeof b == "boolean") {
            return a || b;
        }
        else {
            return a + b;
        }
    }

其调用规则也和前面介绍的重载函数的规则一致,只能以符合 3 种调用签名之一的形式来调用函数,否则会引起编译错误。示例代码如下。

let value1 = combine(1, 2);        //value1为number类型,值为3
let value2 = combine("a", "b");    //value2为string类型,值为"ab"
let value3 = combine(true, false); //value3为boolean类型,值为true
//以下代码将引起编译错误
//编译错误:没有与此调用匹配的重载。 ts(2769)
let value4 = combine(1n, 2n);