# 1. 为函数定义类型

JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):

// 函数声明(Function Declaration)
function sum(x, y) {
    return x + y;
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
    return x + y;
};
1
2
3
4
5
6
7
8
9

一个函数有输入和输出,要在 TypeScript 中对其进行约束,那么就需要把输入和输出都考虑到,下面我们就来看看对这两种函数定义的方式在 TypeScript 中分别都是如何进行约束的。

# 1.1 函数声明

其中函数声明的类型定义较简单:

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

注意,输入多余的(或者少于要求的)参数,是不被允许的

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

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
    return x + y;
}
sum(1);

// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
1
2
3
4
5
6
7
8
9
10
11
12

# 1.2 函数表达式

如果要我们现在写一个对函数表达式(Function Expression)的定义,可能会写成这样:

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

这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:

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

注意不要混淆了 TypeScript 中的 =>ES6 中的 =>

TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

ES6 中,=> 叫做箭头函数,应用十分广泛。

# 1.3 用接口定义函数的类型

我们也可以使用接口的方式来定义一个函数需要符合的形状:

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    return source.search(subString) !== -1;
}
1
2
3
4
5
6
7
8

采用函数表达式|接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变。

# 2. 可选参数和默认参数

TypeScript 里,每个函数参数都是必须的。 这不是指不能传递 nullundefined 作为参数,而是说编译器检查用户是否为每个参数都传入了值。编译器还会假设只有这些参数会被传递进函数。 简单的说,就是传递给一个函数的参数个数必须与函数期望的参数个数一致。

但是在 JavaScript 里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是 undefined。 在 TypeScript 里我们可以在参数名旁使用 ? 实现可选参数的功能。 比如,我们想让 lastName 是可选的:

function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return firstName + "-" + lastName;
  } else {
    return firstName;
  }
}
1
2
3
4
5
6
7

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了

function buildName(firstName?: string, lastName: string) {
    if (firstName) {
        return firstName + ' ' + lastName;
    } else {
        return lastName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName(undefined, 'Tom');

// index.ts(1,40): error TS1016: A required parameter cannot follow an optional parameter.
1
2
3
4
5
6
7
8
9
10
11

另外,在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数。当用户没有传递这个参数或传递的参数值是 undefined 时,此时使用我们提供的默认值,我们把它们叫做有默认初始化值的参数。 让我们修改上例,把firstName 的默认值设置为 "A"

function buildName(firstName: string = "A", lastName?: string): string {
  if (lastName) {
    return firstName + "-" + lastName;
  } else {
    return firstName;
  }
}
1
2
3
4
5
6
7

# 3. 剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在 JavaScript 里,你可以使用 arguments 来访问所有传入的参数。那么在 TypeScript 里,你可以把所有参数都收集到一个变量里。剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意多个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组。

function info(x: string, ...args: string[]) {
  console.log(x, args);
}
info("abc", "c", "b", "a");
1
2
3
4

# 4. 函数重载

所谓函数重载,即函数名相同, 而形参不同的多个同名函数。在 JavaScript 里, 由于弱类型的特点和形参与实参可以不匹配, 是没有函数重载这一说的。但在 TypeScript 里, 由于有了类型系统,所以就有了函数重载。

/* 
 * 函数重载: 函数名相同, 而形参不同的多个函数
 * 需求: 我们有一个add函数,它可以接收2个string类型的参数进行拼接,也
 * 可以接收2个number类型的参数进行相加 
*/

// 重载函数声明
function add(x: string, y: string): string;
function add(x: number, y: number): number;

// 定义函数实现
function add(x: string | number, y: string | number): string | number {
  // 在实现上我们要注意严格判断两个参数的类型是否相等,而不能简单的写一个 x + y
  if (typeof x === "string" && typeof y === "string") {
    return x + y;
  } else if (typeof x === "number" && typeof y === "number") {
    return x + y;
  }
}

console.log(add(1, 2));
console.log(add("a", "b"));
// console.log(add(1, 'a')) // error
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23