MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript声明和调用函数的方法

2021-02-206.1k 阅读

TypeScript 函数声明基础

函数定义的基本语法

在 TypeScript 中,函数声明的基本语法与 JavaScript 类似,但增加了类型标注。函数定义由函数名、参数列表、返回值类型(可选)以及函数体组成。例如,定义一个简单的加法函数:

function add(a: number, b: number): number {
    return a + b;
}

在上述代码中,add 是函数名,ab 是参数,它们的类型都被标注为 number。函数的返回值类型也被明确标注为 number

无返回值函数

如果函数没有返回值(即返回 void),可以这样声明:

function printMessage(message: string): void {
    console.log(message);
}

这里 printMessage 函数接收一个 string 类型的参数 message,并将其打印到控制台,但不返回任何值,所以返回类型是 void

函数表达式

除了使用 function 关键字定义函数,还可以使用函数表达式。例如:

const multiply = function(a: number, b: number): number {
    return a * b;
};

这里通过函数表达式将一个匿名函数赋值给变量 multiply。同样,参数和返回值的类型都进行了标注。函数表达式在 TypeScript 中非常常见,尤其是在需要将函数作为值传递给其他函数或存储在对象属性中的场景。

函数参数类型

必需参数

在前面的例子中,我们看到的参数都是必需参数。例如在 add 函数中,调用时必须提供两个 number 类型的参数:

const result = add(3, 5);

如果少提供或多提供参数,TypeScript 编译器会报错。比如:

// 报错:Expected 2 arguments, but got 1.
const wrongResult = add(3); 

可选参数

有时我们希望函数的某些参数是可选的。在 TypeScript 中,可以通过在参数名后添加 ? 来表示可选参数。例如:

function greet(name: string, greeting?: string): string {
    if (greeting) {
        return `${greeting}, ${name}!`;
    } else {
        return `Hello, ${name}!`;
    }
}

const greeting1 = greet('John');
const greeting2 = greet('Jane', 'Hi');

greet 函数中,greeting 参数是可选的。调用函数时,可以只提供 name 参数,也可以同时提供 namegreeting 参数。

默认参数

TypeScript 还支持为参数提供默认值。当调用函数时没有提供该参数的值时,会使用默认值。例如:

function calculateTotal(price: number, tax: number = 0.1): number {
    return price * (1 + tax);
}

const total1 = calculateTotal(100);
const total2 = calculateTotal(100, 0.05);

calculateTotal 函数中,tax 参数有一个默认值 0.1。如果调用函数时没有提供 tax 的值,就会使用这个默认值。

剩余参数

当函数需要接收不确定数量的参数时,可以使用剩余参数。剩余参数使用 ... 语法,它会将传入的多个参数收集到一个数组中。例如:

function sum(...numbers: number[]): number {
    return numbers.reduce((acc, num) => acc + num, 0);
}

const sumResult1 = sum(1, 2, 3);
const sumResult2 = sum(10, 20);

sum 函数中,...numbers 表示剩余参数,它可以接收任意数量的 number 类型参数,并将它们收集到一个数组 numbers 中,然后通过 reduce 方法计算这些数字的总和。

函数返回值类型

显式返回值类型标注

在前面的例子中,我们已经看到了如何显式标注函数的返回值类型。这有助于 TypeScript 进行类型检查,确保函数实际返回的值与声明的返回值类型一致。例如:

function getLength(str: string): number {
    return str.length;
}

这里明确标注 getLength 函数返回一个 number 类型的值,并且函数体确实返回了 string 的长度,类型匹配。

隐式返回值类型推断

在许多情况下,TypeScript 可以根据函数体的返回语句自动推断出返回值类型,因此可以省略显式的返回值类型标注。例如:

function multiplyByTwo(num: number) {
    return num * 2;
}

虽然没有显式标注返回值类型,但 TypeScript 能推断出该函数返回一个 number 类型的值。不过,为了代码的清晰性和可维护性,建议在复杂函数或可能存在类型歧义的情况下,显式标注返回值类型。

联合类型返回值

函数的返回值类型也可以是联合类型,表示函数可能返回多种类型的值。例如:

function parseValue(input: string): number | null {
    const num = parseInt(input);
    if (!isNaN(num)) {
        return num;
    } else {
        return null;
    }
}

const result1 = parseValue('10');
const result2 = parseValue('abc');

parseValue 函数中,返回值类型是 number | null。如果输入字符串能成功解析为数字,就返回 number 类型的值;否则返回 null

函数重载

函数重载的概念

函数重载允许我们在同一个作用域内定义多个同名函数,但它们的参数列表或返回值类型不同。这在处理不同类型输入但功能相似的情况时非常有用。例如,我们有一个函数 print,既可以打印字符串,也可以打印数字:

function print(value: string): void;
function print(value: number): void;
function print(value: any): void {
    console.log(value);
}

print('Hello');
print(123);

这里我们定义了两个函数签名 print(value: string): voidprint(value: number): void,然后有一个实际的函数实现 print(value: any): void。调用 print 函数时,TypeScript 会根据传入参数的类型选择合适的函数签名进行类型检查。

实现函数重载

在实现函数重载时,需要注意以下几点:

  1. 先定义所有的函数签名,然后再定义实际的函数实现。
  2. 实际的函数实现的参数类型应该是所有函数签名参数类型的联合类型。例如:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
    if (typeof a === 'number' && typeof b === 'number') {
        return a + b;
    } else if (typeof a ==='string' && typeof b ==='string') {
        return a + b;
    }
    throw new Error('Unsupported types');
}

const numResult = add(1, 2);
const strResult = add('Hello', ', World');

这里 add 函数有两个函数签名,分别处理 numberstring 类型的参数。实际实现中根据参数类型进行不同的操作。

箭头函数

箭头函数的基本语法

箭头函数是 TypeScript 中定义函数的一种简洁方式。它的基本语法如下:

const square = (num: number): number => num * num;

这里箭头函数 square 接收一个 number 类型的参数 num,并返回 num 的平方。箭头函数的语法比传统函数定义更紧凑,特别适用于简单的函数表达式。

箭头函数与传统函数的区别

  1. this 绑定:箭头函数没有自己的 this 值,它会捕获其所在上下文的 this。而传统函数在调用时会根据调用方式确定 this 的值。例如:
const obj = {
    value: 10,
    getValue: function() {
        return function() {
            return this.value;
        };
    }
};

const result1 = obj.getValue()(); // undefined

const arrowObj = {
    value: 10,
    getValue: function() {
        return () => this.value;
    }
};

const result2 = arrowObj.getValue()(); // 10

objgetValue 方法中,返回的传统函数中的 this 不是指向 obj,而是 undefined(在严格模式下)。而在 arrowObjgetValue 方法中,返回的箭头函数捕获了 this,指向 arrowObj。 2. 函数名:箭头函数没有自己的名字(匿名函数),而传统函数可以有名字,这在调试和递归调用时会有影响。例如:

function factorial(n: number): number {
    if (n === 0 || n === 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

const arrowFactorial = (n: number): number => {
    if (n === 0 || n === 1) {
        return 1;
    } else {
        return n * arrowFactorial(n - 1); // 报错:arrowFactorial is not defined
    }
};

这里传统函数 factorial 可以在函数内部递归调用自己,而箭头函数 arrowFactorial 在递归调用时会报错,因为箭头函数没有自己的名字。

函数类型与接口

定义函数类型接口

在 TypeScript 中,可以使用接口来定义函数类型。这在将函数作为参数传递或存储在对象属性中时非常有用,能更清晰地定义函数的参数和返回值类型。例如:

interface AddFunction {
    (a: number, b: number): number;
}

const add: AddFunction = function(a: number, b: number): number {
    return a + b;
};

这里定义了一个接口 AddFunction,它描述了一个接收两个 number 类型参数并返回 number 类型值的函数。然后将 add 函数声明为 AddFunction 类型,确保 add 函数的定义符合接口的要求。

使用函数类型接口作为参数

函数类型接口可以作为其他函数的参数类型。例如:

interface MathOperation {
    (a: number, b: number): number;
}

function operate(a: number, b: number, operation: MathOperation): number {
    return operation(a, b);
}

function add(a: number, b: number): number {
    return a + b;
}

function subtract(a: number, b: number): number {
    return a - b;
}

const result1 = operate(5, 3, add);
const result2 = operate(5, 3, subtract);

operate 函数中,第三个参数 operation 的类型是 MathOperation 接口,表示它是一个接收两个 number 类型参数并返回 number 类型值的函数。这样可以将不同的符合该接口的函数(如 addsubtract)作为参数传递给 operate 函数,实现不同的数学运算。

声明和调用异步函数

异步函数基本概念

在处理异步操作(如网络请求、文件读取等)时,TypeScript 提供了异步函数的支持。异步函数使用 async 关键字声明,它返回一个 Promise 对象。例如:

async function fetchData(): Promise<string> {
    return 'Data fetched successfully';
}

这里 fetchData 是一个异步函数,它返回一个 Promise<string>,表示这个 Promise 最终会 resolve 为一个 string 类型的值。

调用异步函数

调用异步函数时,通常使用 await 关键字来等待 Promise 的 resolve。await 只能在 async 函数内部使用。例如:

async function main() {
    const data = await fetchData();
    console.log(data);
}

main();

main 函数中,await fetchData() 会暂停 main 函数的执行,直到 fetchData 返回的 Promise 被 resolve,然后将 resolve 的值赋给 data 变量。

处理异步函数中的错误

异步函数中可能会出现错误,我们可以使用 try...catch 块来捕获错误。例如:

async function fetchData(): Promise<string> {
    throw new Error('Data fetching failed');
}

async function main() {
    try {
        const data = await fetchData();
        console.log(data);
    } catch (error) {
        console.error('Error:', error);
    }
}

main();

fetchData 函数中抛出了一个错误,在 main 函数的 try...catch 块中捕获并处理了这个错误,将错误信息打印到控制台。

函数声明与模块

模块内的函数声明

在 TypeScript 中,模块是一种组织代码的方式。可以在模块内声明函数,并且通过 export 关键字将函数导出,以便在其他模块中使用。例如,创建一个 mathUtils.ts 文件:

export function add(a: number, b: number): number {
    return a + b;
}

export function subtract(a: number, b: number): number {
    return a - b;
}

这里定义了 addsubtract 两个函数,并使用 export 关键字将它们导出。

从模块中导入并调用函数

在其他模块中,可以使用 import 关键字导入模块中导出的函数并调用。例如,在 main.ts 文件中:

import { add, subtract } from './mathUtils';

const result1 = add(5, 3);
const result2 = subtract(5, 3);

这里从 mathUtils.ts 模块中导入了 addsubtract 函数,并在 main.ts 中调用它们。通过模块系统,可以更好地组织和复用代码,避免全局命名冲突。

综上所述,TypeScript 提供了丰富而灵活的函数声明和调用方式,从基本的函数定义、参数和返回值类型标注,到函数重载、箭头函数、异步函数以及模块中的函数声明与调用等,这些特性使得 TypeScript 在开发复杂应用时能够更好地进行类型检查和代码组织,提高代码的可靠性和可维护性。掌握这些方法对于编写高质量的 TypeScript 代码至关重要。