函数的参数与返回值

参数与返回值是函数的重要组成部分,它们将决定函数的调用形式。在 TypeScript 中,你可以定义多种参数,并对返回值进行约束。接下来,将详细介绍。

普通参数与类型推导

TypeScript 中,通过 “参数名称:参数类型” 的方式为函数指明参数。如果传入的参数值不符合函数参数声明的类型,则会引起编译错误,示例代码如下。

function sum(num1: number, num2: number): number {
    return num1 + num2;
}
//编译错误:类型"string"的参数不能赋给类型"number"的参数。ts(2345)
let num3: number = sum("a", "b");

在函数声明时,若省略参数类型,参数类型将默认为 any 类型(任意类型)。示例代码如下。

//编译错误:num1和num2隐式具有 "any"类型,但可以从用法中推断出更好的类型。ts(7044)
function sum(num1, num2): number {
    return num1 + num2;
}
let num3 = sum(1, 2);
//由于参数已经变为any类型,因此以下代码中传入的字符串不会引起编译错误
let num4 = sum("a", "b");

any 类型会绕过编译检查,因此不推荐省略参数类型。

在 Visual Studio Code 中,如果不确定一个函数的参数类型,通过以下步骤进行自动推导。

  1. 将光标移动到参数上,会出现 “快速修复” 选项,选择 “快速修复” 选项,如图8-1所示。

    image 2024 02 18 11 37 04 971
    Figure 1. 图8-1 选择“快速修复”选项
  2. 选择 “从使用情况推导所有类型” 选项,如图8-2所示。

    image 2024 02 18 11 38 14 898
    Figure 2. 图8-2 选择“从使用情况推导所有类型”选项
  3. Visual Studio Code 会自动为函数的参数加上类型声明,如图8-3所示。

    image 2024 02 18 11 39 29 113
    Figure 3. 图8-3 自动为函数的参数加上类型声明

可选参数

默认情况下,函数声明中定义了多少个参数,在调用时就需要传入多少个,而且类型必须符合参数定义,否则会引起编译错误,示例代码如下。

function sum(num1: number, num2: number): number {
    return num1 + num2;
}
//编译错误:应有2个参数,但获得1个。ts(2554)
let num3 = sum(1);
//编译错误:应有2个参数,但获得3个。ts(2554)
let num4 = sum(1,2,3);

然而,在一些情况下,某些参数可能并不是必需的,因此就需要定义可选参数。在 TypeScript 中,通过在参数后面添加问号将参数定义为可选参数。在调用函数时,可选参数的值可传可不传,示例代码如下。

function sum(num1: number, num2?: number, num3?: number): number
{
    let result = num1;
    if (num2) result += num2; // 判空处理
    if (num3) result += num3; // 判空处理
    return result;
}
let num3 = sum(1);       //值为1
let num4 = sum(1, 2);    //值为3
let num5 = sum(1, 2, 3); //值为6

若未向可选参数传递值,则参数值默认为 undefined,示例代码如下。建议做判空处理,只有当可选参数有值时才进行处理。在上一个示例中,通过 if 语句对 num2num3 进行判空处理,如果有值,才会相加。

function sum(num1: number, num2?: number, num3?: number): number
{
    console.log(num2); //输出undefined
    console.log(num3); //输出undefined
}
sum(1);

注意,可选参数必须在必选参数之后定义,否则会引起编译错误,示例代码如下。

//编译错误:必选参数不能位于可选参数后。ts(1016)
function sum(num1?: number, num2: number): number {
    return num1 + num2;
}

默认参数

前一节已经提到,若未向可选参数传递值,则参数值默认为 undefined。此时建议做判空处理,但这样稍显复杂。在 TypeScript 中,你可以将参数定义为默认参数,为参数设置默认值。之后调用函数时,如果向此参数传入值,则使用传入的值;如果未传值,此参数则会使用预先定义的默认值。

默认参数是通过在参数定义后加上 = 默认值 设置的,示例代码如下。

function sum(num1: number, num2: number = 2, num3: number = 3): number
{
    return num1 + num2 + num3;
}

let num4 = sum(1);        //1+2(默认值)+3(默认值)等于6
let num5 = sum(1, 4);     //1+4+3(默认值)等于8
let num6 = sum(1, 4, 5);  //1+4+5等于10

注意,不能将一个参数同时定义为默认参数和可选参数,否则将引起编译错误,示例代码如下。

//编译错误:参数不能包含问号和初始化表达式。ts(1015)
function sum(num1, num2? = 2, num3? = 3): number
{
    return num1 + num2 + num3;
}

如果一个参数为默认参数,即使定义参数时没有指明类型,TypeScript 也会将其推导为默认值的类型。

//num1为any类型,因为num2和num3有数值默认值,所以被推导为数值类型
function sum(num1, num2 = 2, num3 = 3): number
{
    return num1 + num2 + num3;
}
//由于第一个参数为any类型,以下代码不会引起编译错误
sum("a",2,3);
//以下代码会引起编译错误,因为num2和num3被推导为数值类型
//编译错误:类型"string"的参数不能赋给类型"number"的参数。ts(2345)
sum(1,"b","c");

剩余参数

如果传给函数的参数个数不确定,甚至没有上限,用可选参数或默认参数就不合适。在 TypeScript 中,我们可以定义剩余参数,以便接收不限个数的参数。剩余参数必须定义在函数参数列表的末尾,可以以数组或元组的形式定义,调用时函数一个个依次传入,然后以数组或元组的形式在函数体中使用。

  1. 数组型剩余参数

    当定义数组型剩余参数时,参数的定义方式为 “…​参数名称:参数类型[]”。在调用时,传入任意个数的剩余参数,然后以数组的形式在函数体代码中使用,示例代码如下。

    function print(memo: string, ...numbers: number[]): void {
        let printNumberList = "";
    
        if (numbers.length == 0)
            console.log(`${memo}: 未传入剩余参数`);
        else {
            for (let i = 0; i < numbers.length; i++) {
                printNumberList += numbers[i] + ";"
            }
            console.log(`${memo}: ${printNumberList}`);
        }
    }
    print("传入的参数有");                //输出"传入的参数有: 未传入剩余参数"
    print("传入的参数有", 1, 2);          //输出 "传入的参数有: 1;2;"
    print("传入的参数有", 1, 2, 3, 4, 5); //输出 "传入的参数有: 1;2;3;4;5;"

    如果用数组实现前面的求和代码,就可以传入任意个数的参数,代码如下。

    function sum(...numbers:number[]): number
    {
        let result = 0;
        for(let i=0;i<numbers.length;i++)
        {
            result+=numbers[i];
        }
        return result;
    }
    
    let num1 = sum(1);     //值为1
    let num2 = sum(1,2);   //值为3
    let num3 = sum(1,2,3); //值为6
  2. 元组型剩余参数

当定义元组型剩余参数时,参数定义方式为 “…​参数名称:[类型1,类型2,…​,类型n]”。在调用时,你必须传入满足元组定义个数和类型的参数,然后以元组的形式在函数体代码中使用,示例代码如下。

function printTuple(...numbers: [number, string]): void {
    let printNumberList = "";

    for (let i = 0; i < numbers.length; i++) {
        printNumberList += numbers[i] + ";"
    }
    console.log(`传入的参数有${printNumberList}`);
}

printTuple(1,"a"); //输出"传入的参数有1;a;"

注意,如果传入的参数不满足元组定义个数和类型,会引起编译错误,示例代码如下。

function printTuple(...numbers: [number, string]): void {
    let printNumberList = "";

    for (let i = 0; i < numbers.length; i++) {
        printNumberList += numbers[i] + ";"
    }
    console.log(`传入的参数有${printNumberList}`);
}

//编译错误:没有需要 1 参数的重载,但存在需要 0 或 2 参数的重载。ts(2575)
printTuple("a");
//编译错误:类型"string"的参数不能赋给类型"number"的参数。ts(2345)
printTuple("a", 1);
//编译错误:应有0-2个参数,但获得3个。ts(2554)
printTuple(1, "a", 2);

元组本身也支持可选元素与剩余元素,因此元组型剩余参数还可以用以下方式定义,代码如下。

function printTuple(...numbers: [number, boolean?, ...string[]]): void
{
    let printNumberList = "";

    for (let i = 0; i < numbers.length; i++) {
        printNumberList += numbers[i] + ";"
    }
    console.log(`传入的参数有${printNumberList}`);
}

printTuple(1);                      //输出"传入的参数有1;"
printTuple(1, true);                //输出"传入的参数有1;true;"
printTuple(1, true, "a", "b", "c"); //输出"传入的参数有1;true;a;b;c;"

返回值

一个函数可以具有返回值,并可以在声明函数时定义返回值类型,返回值使用 return 语句返回。例如,在以下代码中,函数声明的返回值类型为 number,返回值为数组的长度。

function getArrayLength(array1: string[] = []): number {
    return array1.length;
}
let arrayLength = getArrayLength(["a", "b"]); //值为2

当程序执行到 return 语句时,会结束函数的执行,这意味着 return 语句之后的代码不会执行。示例代码如下。

function sum(num1: number, num2: number): number {
    return num1 + num2;
    console.log("Hello World"); //这句代码永远不会被执行
}

在一个函数中可以有多条 return 语句。例如,以下代码根据分数返回不同的评级字符串。

function getExamResult(score: number): string {
    if (score > 90)
        return "优秀";
    else if (score > 75)
        return "良好";
    else if (score > 60)
        return "及格";
    else
        return "不及格";
}

let res1 = getExamResult(99); //值为"优秀"
let res2 = getExamResult(55); //值为"不及格"

如果声明时的返回值类型和 return 语句的实际返回值类型不符,会引起编译错误,示例代码如下。

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

一个函数可以没有返回值,当没有返回值时,在函数声明中可将返回值声明为 void 类型,表示该函数没有返回值,示例代码如下。

function sayHello(): void {
    console.log("Hello World")!
}

部分情况下,有的函数没有返回值,返回值声明为 void 类型,但它有一些分支可能提前结束函数的执行。此时,你依然可以使用 return 语句,只是 return 语句后不跟返回值,示例代码如下。

function sayHello(name: string = ""): void {
    //如果name为空,则跳出函数
    if (name=="") {
        return;
    }
    //如果name为空,则以下代码不会执行
    console.log("Hello World")!
}

推导返回值类型

TypeScript 能够根据函数体代码中的 return 语句自动推导出返回值类型,因此返回值类型声明可以省略。但基于可读性及编译检查考虑,建议实际编程中,不省略返回值类型。

例如,以下代码声明了一个 test() 函数,它没有显式指定返回值类型,但编译时会自动将其当作返回数值类型的函数。因此调用此函数声明变量时,如果变量无法接收数值类型,就会引起编译错误。

//以下函数根据return语句的内容,推导出函数的返回值类型为number
//以下函数声明等同于function test(num:number):number {...}
function test(num:number) {
    return num;
}

//由于函数的返回值类型为number,因此不能将其值赋给string类型的变量
//编译错误:不能将类型"number"分配给类型"string"。ts(2322)
let a:string = test(1);

当函数有多个不同类型的返回值时,编译时也能自动推导出返回值的类型。例如,以下代码根据 num 参数的值返回不同类型的值。

//以下函数声明等同于function test(num: number): number | true | "a" {...}
function test(num: number) {
    if (num == 1)
        return "a";
    else if (num == 2)
        return true;
    else
        return num;
}