和我一起学习 - TypeScript:处理函数
4.78/5 (6投票s)
深入探讨 TypeScript,讨论函数以及如何使用它们。
上一篇“和我一起学习 - TypeScript”
引言
在上一篇文章中,我们对 TypeScript 进行了介绍,并讨论了它的类型系统。在本文中,我们将深入探讨函数。我们将讨论如何定义和使用函数,以及 TypeScript 允许我们对函数做什么。
如何使用代码
预期您已在 PC 上安装了编辑器和 TypeScript 编译器,并且知道如何编译 TypeScript 文件。如果您还没有这些,请不要惊慌。请查阅本系列的第一部分,“类型研究”。
TypeScript 函数基础
与 JavaScript 一样,TypeScript 也允许您通过声明或表达式来创建函数。
定义函数
函数声明意味着使用 `function` 关键字和有效的函数名称定义函数。该函数将可以通过该名称访问。另一方面,表达式函数不一定有名称,它存储在一个变量中,并且函数可以通过变量名访问。
function declaredFunction () {
}
var expressedFunction = function () {
}
没有名称的函数称为匿名函数。
除了这些,TypeScript 还为我们提供了另一种定义函数的方式,甚至不需要 `function` 关键字。我们可以使用箭头运算符 (=>) 代替。
var arrowOperatorExp = () => {
}
类型标注
既然它的名字是 TypeScript,那么就像变量一样,它也允许我们为函数提供类型标注。我们可以通过函数的返回类型来定义静态类型标注。
首先,让我们在工作文件夹中创建一个名为functions_type-notation.ts的 TypeScript 文件,并将以下代码片段放入其中。
function myFuncDecl1 (): void {
}
var myFuncExp1 = function (): void {
}
var arrowOperatorExp = (): void => {
}
编译它,然后查看.js文件。现在,添加以下几行并再次尝试编译。
function myFuncDecl2 (): void {
return true;
}
编译错误表明编译器对函数的返回类型和函数返回类型的静态标注进行了验证。让我们删除该函数,继续前进。
TypeScript 也允许我们为参数定义静态类型标注。
function sayHello(name: string): void{
}
function saySomething(name: string, num: number): void{
}
在 TypeScript 中,当调用函数时,它会查找与参数类型完全相同、顺序也完全相同的精确签名。遵循此规则,与 JavaScript 不同,一旦为参数定义了静态类型,就不能使用与参数类型不匹配的参数来调用函数。
sayHello(2);
saySomething(2, "The World");
这两行都会引发编译错误,因为它们都不匹配任何函数签名。
可选参数
在 JavaScript 中,函数中的每个参数都是隐式可选参数。但 TypeScript 没有给我们这种“便利”,这是好事。但即使是好事,有时情况也需要函数中的参数是可选的。因此,TypeScript 允许我们拥有显式可选参数。哇,太酷了!开发者现在不仅可以更好地控制函数,还可以更好地控制函数的调用。
要继续此讨论,我们需要另一个名为“functions_optional-parameters.ts”的 TypeScript 文件。输入以下代码片段并尝试编译。
function addNumbersWithOptionalParams(a: number, b: number, c?:number): void {
var res = a + b;
if(c)
{
res += c;
alert('a + b + c = ' + res);
return;
}
alert('a + b = ' + res);
}
addNumbersWithOptionalParams(1);
addNumbersWithOptionalParams(1, 2);
addNumbersWithOptionalParams(1, 2, 3);
第一次调用 `addNumbersWithOptionalParams()` 方法会引发编译错误,但另外两次调用函数则不会。如果我们注释掉有问题的行并进行编译,它将顺利编译。
但请记住一件事。在开发带有可选参数的函数时,它们必须包含在函数签名中必需参数的后面。如果存在多个可选参数,我们可以使用参数来选择性地传递。我们可以省略函数签名末尾参数的参数。但是,如果我们需要跳过中间的某些参数,我们将需要为这些参数传递 `null` 或 `undefined`。例如,如果我们的函数签名是
function addAndOptionalMultiply (a: number, b: number, c?:number, m?:number): void {};
要使用 `a`、`b` 和 `m`,需要如下调用:
addAndOptionalMultiply (1, 2, null, 3);
默认参数
如果我们定义了一个带有某些函数参数的函数,在使用这些参数之前,我们需要检查它们是否包含任何值。如果调用者未为可选参数传递任何值,是否有一种机制可以为它们设置默认值,这不是很棒吗?是的,除了可选参数之外,TypeScript 还允许我们使用默认参数。让我们创建一个名为“functions_default-parameters.ts”的新文件。输入以下代码片段并编译。
function addNumbersWithDefaultParameters(a: number, b: number, c:number = 0, d: number = 0): void {
var res = a + b + c + d;
alert('result = ' + res);
}
addNumbersWithDefaultParameters(1, 2);
addNumbersWithDefaultParameters(1, 2, 3)
因为我们在函数中使用了默认参数,所以不必检查可选变量是否未定义。当我们想更改现有函数以增加更多参数时,这非常方便。让我们也看看输出的 JavaScript 文件,以便更好地理解默认参数。
function addNumbersWithDefaultParameters(a, b, c, d) {
if (c === void 0) { c = 0; }
if (d === void 0) { d = 0; }
var res = a + b + c + d;
alert('result = ' + res);
}
addNumbersWithDefaultParameters(1, 2);
addNumbersWithDefaultParameters(1, 2, 3);
剩余参数
我们已经了解了可选参数,也了解了默认参数。这两者都有一个共同的问题——传递参数的限制。我们不能为只接受四个参数的函数传递六个参数。但不用担心,我们有剩余参数来支持这一点。在参数名称前加上省略号,并将数组用作类型即可完成工作。
创建一个名为“functions_rest-parameters.ts”的新文件。将以下代码片段放入其中并进行编译。
function addNumbersWithRestParameters(... n: number[]): void {
var result = 0;
var i;
for(i=0; i < n.length; i++) {
result += n[i];
}
alert("Result = " + result);
}
addNumbersWithRestParameters(2);
addNumbersWithRestParameters(2, 2, 2, 2, 2);
看看 JavaScript 文件
function addNumbersWithRestParameters() {
var n = [];
for (var _i = 0; _i < arguments.length; _i++) {
n[_i - 0] = arguments[_i];
}
var result = 0;
var i;
for (i = 0; i < n.length; i++) {
result += n[i];
}
alert("Result = " + result);
}
addNumbersWithRestParameters(2);
addNumbersWithRestParameters(2, 2, 2, 2, 2);
有趣的是,函数中没有参数。那么,它是如何接收参数的呢?如果您不熟悉 JavaScript 中的 `arguments` 对象,它是一个内置对象,在函数内部作用域,它的大部分行为都像一个数组。当调用函数时,`arguments` 对象会包含所有参数,并且可以从函数中访问。
正如您所见,尽管它在 TypeScript 中看起来很简洁,但它会引入一个冗余的循环,将 `arguments` 对象中的项分配给本地变量,因此存在性能问题。
函数重载
TypeScript 支持函数重载。如果您不知道我说的是什么,函数重载就是拥有多个同名但签名不同(参数类型或数量不同)的函数的能力。
一如既往,我们将编写一些代码。让我们在工作文件夹中创建一个名为“functions_overloading.ts”的文件。键入以下代码并进行编译。
function print (n: number);
function print (str: string);
function print (b: boolean);
function print (v: number | string | boolean) {
if(typeof(v) === "number")
{
alert(v + " is a number")
}
else if(typeof(v) === "string")
{
alert("'" + v + "' is a string")
}
else if(typeof(v) === "boolean")
{
alert("'" + v + "' is a boolean")
}
else {
alert("error");
}
}
print(1);
print("Hello World!");
print(true);
看看输出的 JavaScript 文件。难道我们为编写 TypeScript 而过度劳累,写一个 JavaScript 函数不是更简单吗?嗯,是的,但这里的好处是:JavaScript 中的 `print()` 函数允许接受任何参数,并且很可能经常导致错误。另一方面,由于 TypeScript 是类型化的,您不能传递类型不是 `number`、`string` 和 `bool` 的任何参数。您可以尝试通过添加以下行来验证这一点:
print({id: 1, name: "test"});
回调
与 JavaScript 一样,TypeScript 也允许将回调函数作为参数传递。与其他所有事物一样,它们也可以被类型化。让我们看看如何。创建一个名为“functions_callbacks.ts”的新文件,并用以下几行填充它。
var mainFunction = function(callback: () => void){
alert("Main Function");
callback();
}
var callbackFunction = function(): void {
alert("Callback");
}
mainFunction(callbackFunction);
完成后,保存并编译。很简单,我们有一个接受任何 `void` 回调函数的 `main` 函数,在执行过程中,它会执行回调。“Arrow”函数定义在处理回调时非常有用。
mainFunction((): void => {
alert("Arrow Function Callback");
})
立即执行函数
正如我们在上一集 类型研究 中学到的,`var` 变量的作用域是局部的函数。立即执行函数是一种设计模式,它利用这个原理为使用范围有限的变量创建更小的作用域。创建一个名为“functions_iif.ts”的新文件,并键入以下几行。
function iif_main(): void {
var gVar: number = 1;
(function(v: number){
gVar = v;
var lVar: number = v;
alert("gVar inside IIF=" + gVar);
alert("lVar inside IIF=" + lVar);
})(2);
alert("gVar in mainFunction = " + gVar);
alert("lVar in mainFunction = " + lVar); //Error
}
iif_main();
编译。在立即执行函数中声明的 `lVar` 在外部函数的范围内不可用。
结论
现在我们了解了函数的基础知识,我们可以进入 TypeScript 的面向对象世界了。但这将是另一个故事。
