函数的声明与调用

函数可以用 function 关键字通过多种形式声明。接下来,将分别介绍这些声明方式,以及对应的调用方式。

以普通方式声明与调用

普通方式的声明语法如下,这是最常用的方式。

function 函数名称(参数1:类型,参数2:类型,...,参数n:类型):返回值类型 {
    // 函数体代码
    // 如果有返回值,则需要写"return 返回值;"语句
}
bash

调用语法如下。

函数名称(参数值1,参数值2,...,参数值n)
bash

在进行声明时,只有函数名称是必需的,参数及其类型、返回值类型都是可选的。该形式的声明示例及调用示例如下。

function sum(num1: number, num2: number): number {
    return num1 + num2;
}
let num3: number = sum(1, 3); //num3的值为4
//以下函数没有参数,并省略返回值类型
function sayHelloWorld() {
    console.log("Hello World!");
}
sayHelloWorld();              //输出"Hello World!"
typescript

在本例中先定义了一个 sum() 函数,它接收两个数值类型参数,对它们求和并作为返回值返回。在为变量 num3 赋值时调用了 sum() 函数,传入了 1 和 3,返回值为 4,并将该返回值赋给变量 num3。然后,定义了一个名为 sayHelloWorld 的参数,它没有参数声明,因此调用时也无须传入参数,直接调用就会输出 “Hello World!” 字符串。

对于以普通方式声明的函数,作用域会提升到当前作用域的顶端,因此函数可以在声明语句出现之前就调用。示例代码如下。

let num3: number = sum(1, 3); //num3的值为4

function sum(num1: number, num2: number): number {
    return num1 + num2;
}
typescript

通过表达式声明与调用

函数也可以通过表达式声明,将其值赋给变量或常量,语法如下。

let 变量名称 = function(参数1:类型,参数2:类型,...,参数n:类型):返回值类型 {
    // 函数体代码
}
typescript

调用语法如下。

变量名称(参数值1,参数值2,...,参数值n)
bash

这种方式声明的函数实际上是匿名函数,即没有函数名称的函数,存放在变量中的函数不需要函数名,它们始终使用变量名来调用。在声明时,除参数及其类型之外,返回值类型也是可选的。该方式的声明示例及调用示例如下。

let multiplication = function (num1: number, num2: number): number { return num1 * num2; };
let num3: number = multiplication(4, 3); //num3的值为12
//以下函数没有参数,但有返回值
let circumference = function (): number { return 3.14159 };
let num4: number = circumference();      //num3的值为3.14159
typescript

注意,通过表达式声明的函数的作用域和变量的一致,因此不存在提升的情况,只能在声明之后调用,否则会引起编译错误。示例代码如下。

//编译错误:声明之前已使用的块范围变量"multiplication"。ts(2448)
let num3: number = multiplication(4, 3);
let multiplication = function (num1: number, num2: number): number { return num1 * num2; };
typescript
  1. 箭头函数

    ECMAScript 6 新增了胖箭头(=>)语法来声明函数表达式,因此你也可以将以表达式声明的 function 语句部分替换为箭头函数语句,其语法如下。

    let 变量名称 = (参数1:类型,参数2:类型,...,参数n:类型):返回值类型 => {
        // 函数体代码
    }
    bash

    在声明时,除参数及其类型之外,返回值类型也是可选的。前面的表达式声明也可以使用箭头函数实现,代码如下。

    let multiplication = (num1: number, num2: number): number => { return num1 * num2; };
    let num3: number = multiplication(4, 3); //num3的值为12
    
    //以下函数没有参数,但有返回值
    let circumference = (): number => { return 3.14159 };
    let num4: number = circumference(); //num3的值为3.14159
    typescript

    箭头函数简洁的语法非常适合嵌入函数的场景。例如,对于前面提到的数组的方法 findIndex(),需要编写自定义筛选函数,如果用箭头函数来进行声明,函数将显得非常精简。

    //使用匿名函数之前
    //以下自定义函数用于判断当前元素值是否大于10
    function myFunction(value, index, array) {
        return value > 10;
    }
    let numbers: number[] = [4, 7, 9, 11, 15, 20];
    //首个匹配的值是11, 以下代码输出3
    console.log(numbers.findIndex(myFunction));
    
    //使用匿名函数之后
    let numbers: number[] = [4, 7, 9, 11, 15, 20];
    console.log(numbers.findIndex((value, index, array) => { return value > 10; }));
    typescript

    箭头函数在只有单个参数时可以省略圆括号,而在只有单条执行语句时可以省略花括号。如果单条执行语句是表达式,还可以省略 return 关键字,直接将其作为返回值返回,示例代码如下。

    let square = a => a * a;
    let num1 = square(2); //num1的值为4
    typescript

    箭头函数和以 function 关键字声明的函数在用法上是类似的,很多可以使用 function 关键字声明的地方可以使用箭头函数。但箭头函数毕竟是简化版,有局限性,例如,箭头函数不能使用 thisarguments 等内置函数对象。

  2. Function() 构造函数

    我们还可以使用 Function() 构造函数来创建函数,具体语法如下。

    let 变量名称 = new Function("参数1","参数2",...,"参数N","函数体代码");
    bash

    声明及调用示例如下。

    let sum = new Function("num1", "num2", "return num1+num2;");
    let num3 = sum(1,3); //num3的值为4
    bash

    不推荐使用这种方式声明函数,原因如下。

    • 这种方式会绕过 TypeScript 的编译检查。

    • 这种创建方式会造成两次解释:第一次,将它的声明语句当作常规 ECMAScript 代码进行解释与执行;第二次,把解释传给构造函数的字符串,这会影响性能。

特殊的声明与调用方式

还有一些比较特殊的声明与调用形式,下面将进行简单介绍。

  1. 自调用函数

    函数表达式可以 “自调用”,只需在声明匿名函数时将声明语句包含在圆括号中,并且在表达式后补上另一个圆括号即可。例如,在以下代码中,函数在声明后会立即自动执行。

    (function () {
        console.log(`Hello World!`);
    })();
    
    (function (name:string) {
        console.log('Hello ${name}!');
    })("Rick");
    typescript

    代码执行后输出结果如下。

    > Hello World!
    > Hello Rick!
    bash
  2. 参数函数

    函数可以作为另一个函数的参数值传递给另一个参数。例如,以下代码定义一个 SplitNameAndSayHello() 函数,它不仅接收一个名为 name、类型为 string 的参数,还接收一个名为 func、类型为特定函数格式的参数,并在函数体代码中调用这个传入的参数函数。

    function SplitNameAndSayHello(name: string, func: (firstName: string) => void) {
    //以下代码以空格为分隔符,将字符串分隔为字符串数组,并取第一个值
    let names = name.split(" ");
        let firstName = names[0];
        func(firstName);
    }
    //以下代码执行后输出Hello Real
    SplitNameAndSayHello("Real Zhao", SayHelloToSomeone);
    
    function SayHelloToSomeone(firstName: string) {
        console.log(`Hello ${firstName}!`);
    }
    typescript

    参数函数通常用于需要传入自定义函数的内置方法,或者用于指定回调函数。

  3. 递归函数

递归函数是一种在函数体代码中直接或间接调用自身的函数。递归函数通常只需少量代码就可以进行复杂的计算。

例如,通过递归函数,求自然数 1, 2, 3, …, n 的和,代码如下。

let n = 100;
let result = sum(n); //result=5050, 即1+2+3+…+100的和为5050
function sum(n: number) {
    if (n == 1) return 1;
    return sum(n - 1) + n;
}
typescript

通过递归求解 xy,代码如下。

let x = 5, y = 3;
let result = power(x, y); //result=125,即5的3次方为125
function power(x: number, y: number) {
    if (y <= 1) return x;
    else return x * power(x, y - 1);
}
typescript

递归函数必须要有退出条件,否则会一直递归执行,直到运行报错,引起调用栈溢出,示例代码如下。

function consoleLog() {
    consoleLog();
}
consoleLog();
typescript

运行代码后,报错如下。

> Uncaught RangeError: Maximum call stack size exceeded
bash