函数是JavaScript中任何应用的基本构件。它们是你如何建立抽象层,模仿类、信息隐藏和模块。在TypeScript中,虽然有类、命名空间和模块,但函数仍然在描述如何做事情方面扮演着关键的角色。TypeScript还为标准的JavaScript函数增加了一些新的功能,使它们更容易使用。
函数
首先,就像在JavaScript中一样,TypeScript函数可以被创建为一个命名函数或匿名函数。这允许你为你的应用程序选择最合适的方法,无论你是在一个API中建立一个函数列表,还是一个一次性函数交给另一个函数。
快速回顾一下这两种方法在JavaScript中是什么样子的。
// Named functionfunction add(x, y) {return x + y;}// Anonymous functionlet myAdd = function (x, y) {return x + y;};
就像在JavaScript中一样,函数可以引用函数体之外的变量。当它们这样做时,它们被称为捕获这些变量。虽然理解这如何工作(以及使用这种技术时的权衡)超出了本文的范围,但牢固理解这种机制如何工作是使用JavaScript和TypeScript的一个重要部分。
let z = 100;function addToZ(x, y) {return x + y + z;}
功能类型
输入函数
让我们在前面的简单例子中加入类型。
function add(x: number, y: number): number {return x + y;}let myAdd = function (x: number, y: number): number {return x + y;};
我们可以给每个参数添加类型,然后给函数本身添加返回类型。TypeScript可以通过查看返回语句来计算出返回类型,所以在很多情况下,我们也可以选择不写这个。
编写函数类型
现在我们已经将函数类型化了,让我们通过观察函数类型的每一块,将函数的完整类型写出来。
let myAdd: (x: number, y: number) => number = function (x: number,y: number): number {return x + y;};
一个函数的类型同样有两部分:参数的类型和返回类型。在写出整个函数类型的时候,这两部分都是必须的。我们在写出参数类型的时候,就像写参数列表一样,给每个参数起一个名字和一个类型。这个名字只是为了帮助阅读性。我们可以改写成
let myAdd: (baseValue: number, increment: number) => number = function (x: number,y: number): number {return x + y;};
只要参数类型一致,就认为是函数的有效类型,不管你在函数类型中给参数起什么名字。
第二部分是返回类型。我们通过在参数和返回类型之间使用一个箭头(=>)来明确哪个是返回类型。如前所述,这是函数类型中的必填部分,所以如果函数没有返回值,你会使用void而不是不写。
值得注意的是,只有参数和返回类型构成了函数类型。捕获的变量不会反映在类型中。实际上,捕获的变量是任何函数的 “隐藏状态 “的一部分,并不构成其API。
推断类型
在玩这个例子的时候,你可能会注意到TypeScript编译器可以计算出类型,即使你在等式的一边只有类型。
// The parameters 'x' and 'y' have the type numberlet myAdd = function (x: number, y: number): number {return x + y;};// myAdd has the full function typelet myAdd2: (baseValue: number, increment: number) => number = function (x, y) {return x + y;};
这就是所谓的 “上下文类型”,一种类型推理的形式。这有助于减少你的程序的类型化工作量。
可选和默认参数
在TypeScript中,每个参数都被假定为函数所需。这并不意味着它不能被赋予null或未定义,而是当函数被调用时,编译器会检查用户是否为每个参数提供了一个值。编译器还假设这些参数是唯一会传递给函数的参数。简而言之,给函数的参数数必须与函数期望的参数数相匹配。
function buildName(firstName: string, lastName: string) {return firstName + " " + lastName;}let result1 = buildName("Bob"); // error, too few parametersExpected 2 arguments, but got 1.let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parametersExpected 2 arguments, but got 3.let result3 = buildName("Bob", "Adams"); // ah, just right
在JavaScript中,每一个参数都是可选的,用户可以根据自己的需要将它们去掉。当他们这样做时,他们的值是未定义的。在TypeScript中,我们可以通过在我们希望成为可选项的参数末尾添加一个?来获得这个功能。例如,让我们说我们希望上面的姓氏参数是可选的。
function buildName(firstName: string, lastName?: string) {if (lastName) return firstName + " " + lastName;else return firstName;}let result1 = buildName("Bob"); // works correctly nowlet result2 = buildName("Bob", "Adams", "Sr."); // error, too many parametersExpected 1-2 arguments, but got 3.let result3 = buildName("Bob", "Adams"); // ah, just right
任何可选参数必须跟在所需参数后面。如果我们想让第一个名字成为可选的,而不是最后一个名字,我们需要改变函数中参数的顺序,将第一个名字放在列表的最后。
在TypeScript中,我们也可以设置一个参数的值,如果用户没有提供一个参数,或者如果用户在它的位置上传递了undefined,那么这个值将被分配。这些被称为默认-初始化参数。让我们以前面的例子为例,将姓氏默认为 “Smith”。
function buildName(firstName: string, lastName = "Smith") {return firstName + " " + lastName;}let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parametersExpected 1-2 arguments, but got 3.let result4 = buildName("Bob", "Adams"); // ah, just right
在所有必要参数之后的默认初始化参数被视为可选参数,就像可选参数一样,在调用各自的函数时可以省略。这意味着可选参数和后面的缺省参数在类型上会有共性,所以这两个
function buildName(firstName: string, lastName?: string) {// ...}
和
function buildName(firstName: string, lastName = "Smith") {// ...}
共享同一类型(firstName: string, lastName?: string) => string。lastName的默认值在类型中消失,只留下参数是可选的事实。
与普通的可选参数不同,默认初始化参数不需要出现在所需参数之后。如果一个默认初始化参数出现在需要参数之前,用户需要显式传递undefined来获得默认初始化值。例如,我们可以写上一个例子,只在firstName上写一个默认初始化值。
function buildName(firstName = "Will", lastName: string) {return firstName + " " + lastName;}let result1 = buildName("Bob"); // error, too few parametersExpected 2 arguments, but got 1.let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parametersExpected 2 arguments, but got 3.let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"
