使用 TypeScript 编写函数 day4

803 阅读5分钟

这是我参与11月更文挑战的第13天,活动详情查看:11月更文挑战 如果你知道如何在 JavaScript 中编写函数,那么也知道如何在 TypeScript 中编写函数。 但 TypeScript 为标准 JavaScript 函数添加了一些新功能,使它们更易于使用。

在 TypeScript 中创建函数

在 JavaScript 中,函数定义不指定形参的数据类型,不对传递的实参执行类型检查,也不检查接收的实参数量,因此必须为函数添加检查这些形参的逻辑。

TypeScript 简化了函数开发,通过允许键入参数和返回值,使它们更易于进行故障排除。 TypeScript 还为参数添加了新选项。 例如,虽然在 JavaScript 函数中,所有参数都是可选的,但你可以在 TypeScript 中将参数设置为必需的或可选的。

命名函数

命名函数是使用关键字 function 编写的函数声明,在当前范围内以不同名称提供。 在运行任何代码之前,命名函数声明会加载到执行上下文中。 这称为提升,这意味着你可以在声明函数之前使用该函数。

在 TypeScript 中声明命名函数的语法与在 JavaScript 中定义该函数的语法相同。 与 TypeScript 的唯一区别在于,你现在可以为函数的参数和返回值提供类型注释。

此函数接受两个 number 类型的参数,并返回 number

function addNumbers (x: number, y: number): number {
   return x + y;
}
addNumbers(1, 2);

匿名函数

函数表达式(或匿名函数)是未预先加载到执行上下文中的函数,并且仅当代码遇到该函数时才会运行。 函数表达式是在运行时创建的,并且必须先声明才能调用。 (这意味着不会对它们进行提升,而命名函数声明在程序开始执行时就会进行提升,并且可以在其声明之前调用。)

函数表达式表示值,因此通常会将这些值分配给变量或传递给其他函数,这意味着函数没有名称。

此示例将 function 表达式赋值给变量 addNumbers。 请注意,函数将代替函数名,这使得函数是匿名的。 现在可以使用此变量调用函数。

let addNumbers = function (x: number, y: number): number {
   return x + y;
}
addNumbers(1, 2);

这会显示前面示例中的 sum 命名函数在作为匿名函数编写时的样子。 请注意,名称 add 已替换为函数,该函数已在变量声明中作为表达式实现。

let total = function (input: number[]): number {
    let total: number =  0;
    for(let i = 0; i < input.length; i++) {
        if(isNaN(input[i])) {
            continue;
        }
        total += Number(input[i]);
    }
    return total;
}
​
console.log(total([1, 2, 3]));

如前所述,在使用匿名函数时,你将获得类型检查和 智能感知。你还会注意到,在此示例中,变量 total 不是类型化的变量,但 TypeScript 可以通过称为“上下文类型化”的内容(一种类型推理形式)来确定其类型。 这可以减少保持程序类型所需的工作量。

箭头函数

箭头函数(也称为 Lambda 或胖箭头函数,因为定义它们的是 => 运算符)提供用于定义匿名函数的简写语法。 由于其简洁性,箭头函数通常用于简单的函数和某些事件处理场景。

此示例将匿名 function 语法与单行箭头函数进行比较。 箭头函数通过省略函数关键字并在参数和函数体之间添加 => 运算符来简化语法。

// Anonymous function
let addNumbers1 = function (x: number, y: number): number {
   return x + y;
}
​
// Arrow function
let addNumbers2 = (x: number, y: number): number => x + y;

在此示例中,还请注意,大括号已移除,并且没有 return 语句。

如果函数体有多行,则用大括号括起来,并包含 return 语句(如适用)。此示例显示前面示例中的匿名函数在作为箭头函数编写时的样子。

let total2 = (input: number[]): number => {
    let total: number =  0;
    for(let i = 0; i < input.length; i++) {
        if(isNaN(input[i])) {
            continue;
        }
        total += Number(input[i]);
    }
    return total;
}

运用参数的乐趣

默认情况下,TypeScript 编译器假定在函数中定义的所有参数都是必需的。 调用函数时,TypeScript 编译器将验证:

  • 已为每个参数提供值。
  • 仅将函数所需的参数传递给它。
  • 按在函数中定义的顺序传递参数。

这不同于 JavaScript,后者假定所有形参都是可选的,并允许你向函数传递比函数定义的更多(或更少)的实参。

除了必需参数外,还可以定义函数和可选参数、默认参数、rest 参数以及析构对象参数。

必需的参数

除非另行指定,否则所有函数形参都是必需的,并且传递给函数的实参数目必须与该函数所需的必需形参数目匹配。

在此示例中,所有参数都是必需的。

function addNumbers (x: number, y: number): number {
   return x + y;
}
​
addNumbers(1, 2); // Returns 3
addNumbers(1);    // Returns an error

可选参数

还可以通过在参数名后面附加问号 (?) 来定义可选参数。

在此示例中,x 是必需的,y 是可选的。

function addNumbers (x: number, y?: number): number {
   return x + y;
}
​
addNumbers(1, 2); // Returns 3
addNumbers(1);    // Returns 1

默认参数

还可以为可选参数分配默认值。 如果将值作为实参传递给可选形参,则将向其分配该值。 否则,将为它分配默认值。 与可选参数一样,默认参数必须位于参数列表中所需的参数之后。

在此示例中,x 是必需的,y 是可选的。 如果值没有传递给 y,则默认值为 25

function addNumbers (x: number, y = 25): number {
   return x + y;
}
​
addNumbers(1, 2);  // Returns 3
addNumbers(1);     // Returns 26

rest 参数

如果要使用多个参数作为一个组(在数组中)或不知道函数最终将采用的参数数量,则可以使用 rest 参数。 rest 参数被视为无限数量的可选参数。 可以将它们保留不动,或根据需要调整数量。

此示例包含一个必需参数和一个可选参数 restOfNumbers,该参数可接受任意数量的其他数字。 restOfNumbers 之前的省略号 (...) 指示编译器构建一个传递给函数的参数数组,并给它后面的名称赋值,这样你就可以在函数中使用它。

let addAllNumbers = (firstNumber: number, ...restOfNumbers: number[]): number => {
   let total: number =  firstNumber;
   for(let counter = 0; counter < restOfNumbers.length; counter++) {
      if(isNaN(restOfNumbers[counter])){
         continue;
      }
      total += Number(restOfNumbers[counter]);
   }
   return total;
}

函数现在可以接受一个或多个值并返回结果。

addAllNumbers(1, 2, 3, 4, 5, 6, 7);  // returns 28
addAllNumbers(2);                    // returns 2
addAllNumbers(2, 3, "three");        // flags error due to data type at design time, returns 5

析构对象参数

函数参数是有位置的,并且必须按照它们在函数中定义的顺序传递。 在调用具有多个可选参数或相同数据类型的函数时,这可能会降低代码的可读性。

若要启用命名参数,可以使用称为析构对象参数的技术。 这使你能够在函数中使用接口来定义命名参数,而不是定位参数。

以下示例定义了一个接口 Message,该接口又定义了两个属性。 在 displayMessage 函数中,Message 对象作为参数传递,提供对属性的访问,就像它们是普通参数一样。

interface Message {
   text: string;
   sender: string;
}
function displayMessage({text, sender}: Message) {
    console.log(`Message from ${sender}: ${text}`);
}
displayMessage({sender: 'Christopher', text: 'hello, world'});

练习 - 定义函数类型

可以定义函数类型,然后在函数中使用它们。 如果要对多个函数应用相同的函数类型签名,这会很有用。

可以使用类型别名或接口来定义函数类型。 这两种方法本质上都是相同的,因此由你决定哪种方法最适合。 如果希望选择扩展函数类型,接口是更好的选择。 如果要使用联合或元组,则类型别名更好。

假设你要创建一个函数,该函数根据传递给它的参数值来执行加法运算或减法运算。 加法和减法运算都接受两个数字 xy,并将结果作为数字返回。

  1. 使用类型别名定义名为 calculator 的函数类型。 类型签名有一个参数列表 (x: number, y: number) 并返回 number,以箭头 (=>) 运算符分隔。 (请注意,类型签名的语法与箭头函数相同。)

    type calculator = (x: number, y: number) => number;
    
  2. 现在可以在声明函数时使用函数类型作为类型签名。 声明函数类型 calculator 的两个变量,一个用于加法运算,一个用于减法运算。 通过将每个函数的结果返回到控制台来测试新函数。

    let addNumbers: calculator = (x: number, y: number): number => x + y;
    let subtractNumbers: calculator = (x: number, y: number): number => x - y;
    ​
    console.log(addNumbers(1, 2));
    console.log(subtractNumbers(1, 2));
    
  3. 还可以使用 calculator 函数类型从其他函数传递值。 输入 doCalculation 函数,该函数根据传递给 operation 参数的值返回 addNumberssubtractNumbers 函数的结果。

    let doCalculation = (operation: 'add' | 'subtract'): calculator => {
        if (operation === 'add') {
            return addNumbers;
        } else {
            return subtractNumbers;
        }
    }
    
  4. 通过输入 console.log(doCalculation('add')(1, 2)) 尝试运行函数,你会注意到,TypeScript 能够基于 doCalculationcalculator 中定义的类型提供 Intellisense 帮助。

  5. 现在,注释掉 calculator 函数类型,并使用接口声明一个新函数类型。 请注意,类型签名略有不同,用冒号 (:) 分隔参数列表和返回类型,而不是箭头操作符。 将 calculator 函数替换为变量声明中的 Calculator 接口。 完成后,代码的工作原理应该相同。

    // type calculator = (x: number, y: number) => number;
    interface Calculator {
        (x: number, y: number): number;
    }
    

函数类型推理

定义函数时,函数参数的名称不需要与函数类型中的名称匹配。 尽管需要在这两个位置中命名类型签名中的参数,但在检查两个函数类型是否兼容时,将忽略这些名称。

还可以保留参数类型和返回类型,因为 TypeScript 将从函数类型推断这些类型。

就 TypeScript 而言,这三个语句是相同的。

let addNumbers: Calculator = (x: number, y: number): number => x + y;
let addNumbers: Calculator = (number1: number, number2: number): number => number1 + number2;
let addNumbers: Calculator = (num1, num2) => num1 + num2;