这是我参与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'});
复制代码
练习 - 定义函数类型
可以定义函数类型,然后在函数中使用它们。 如果要对多个函数应用相同的函数类型签名,这会很有用。
可以使用类型别名或接口来定义函数类型。 这两种方法本质上都是相同的,因此由你决定哪种方法最适合。 如果希望选择扩展函数类型,接口是更好的选择。 如果要使用联合或元组,则类型别名更好。
假设你要创建一个函数,该函数根据传递给它的参数值来执行加法运算或减法运算。 加法和减法运算都接受两个数字 x
和 y
,并将结果作为数字返回。
-
使用类型别名定义名为
calculator
的函数类型。 类型签名有一个参数列表(x: number, y: number)
并返回number
,以箭头 (=>
) 运算符分隔。 (请注意,类型签名的语法与箭头函数相同。)type calculator = (x: number, y: number) => number; 复制代码
-
现在可以在声明函数时使用函数类型作为类型签名。 声明函数类型
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)); 复制代码
-
还可以使用
calculator
函数类型从其他函数传递值。 输入doCalculation
函数,该函数根据传递给operation
参数的值返回addNumbers
或subtractNumbers
函数的结果。let doCalculation = (operation: 'add' | 'subtract'): calculator => { if (operation === 'add') { return addNumbers; } else { return subtractNumbers; } } 复制代码
-
通过输入
console.log(doCalculation('add')(1, 2))
尝试运行函数,你会注意到,TypeScript 能够基于doCalculation
和calculator
中定义的类型提供 Intellisense 帮助。 -
现在,注释掉
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;
复制代码