# 函数类型

# 基本用法

简单的定义函数写法:

function add(arg1: number, arg2: number): number {
  return x + y;
}
// 或者箭头函数
const add = (arg1: number, arg2: number): number => {
  return x + y;
};

说明:上面参数 arg1 和 arg2 都是数值类型,最后通过相加得到的结果也是数值类型。

特点:

  • 如果在这里省略参数的类型,TypeScript 会默认这个参数是 any 类型;

  • 如果省略返回值的类型,如果函数无返回值,那么 TypeScript 会默认函数返回值是 void 类型;

  • 如果函数有返回值,那么 TypeScript 会根据我们定义的逻辑推断出返回类型。

# 函数类型

一个函数的定义包括函数名、参数、逻辑和返回值。

上面的例子中,我们都是在定义函数的时间直接指定参数类型和返回值类型。接下来,先来看例子然后再进行解释:

let add: (x: number, y: number) => number;

add = (arg1: number, arg2: number): number => arg1 + arg2;
add = (arg1: string, arg2: string): string => arg1 + arg2; // error

上面这个例子中,我们首先定义了一个变量 add,给它指定了函数类型,也就是(x: number, y: number) => number

然后,我们给 add 赋了一个实际的函数,这个函数参数类型和返回类型都和函数类型中定义的一致,所以可以赋值。

后面我们又给它赋了一个新函数,而这个函数的参数类型和返回值类型都是 string 类型,这时就会报如下错误:

不能将类型"(arg1: string, arg2: string) => string"分配给类型"(x: number, y: number) => number"。
  参数"arg1""x" 的类型不兼容。
    不能将类型"number"分配给类型"string"

函数中如果使用了函数体之外定义的变量,这个变量的类型是不体现在函数类型定义的。

# 如何定义函数类型

除了上面在定义函数的时候,直接定义函数的类型与返回值以外,还有另外的两种方法:

# *使用Interface

使用接口可以清晰地定义函数类型,直接跳转学习Interface

还拿上面的 add 函数为例:

interface Add {
  (x: number, y: number): number;
}

let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; // error 不能将类型“(arg1: string, arg2: string) => string”分配给类型“Add”

# *使用类型别名

我们可以使用类型别名来定义函数类型,类型别名我们在后面讲到高级类型的时候还会讲到。

我们来看一下具体的写法:

type Add = (x: number, y: number) => number;

let add: Add = (arg1: string, arg2: string): string => arg1 + arg2; // error 不能将类型“(arg1: string, arg2: string) => string”分配给类型“Add”

使用type关键字可以为原始值、联合类型、元组以及任何我们定义的类型起一个别名。上面定义了 Add 这个别名后,Add就成为了一个和(x: number, y: number) => number一致的类型定义。例子中定义了Add类型,指定add类型为Add,但是给add赋的值并不满足Add类型要求,所以报错了。

# 参数

# 可选参数

用法:可选参数只需在参数名后跟随一个?即可;

let add: Add = (arg1: number, arg2?: number): string => arg1 + arg2;

add(1)
add(1, 2)

type Add = (x?: number, y: number) => number; // error 必选参数不能位于可选参数后。

说明:

  • 可选参数必须放到最后才行(放置在必选参数之后);
  • 而如果几个参数中,前面的参数是可不传的,后面的参数是需要传的,就需要在该可不传的参数位置传入一个 undefined 占位。

# 默认参数

用法:定义函数时,直接在参数后面使用等号连接默认值即可;

说明:

  • 带默认值的参数则可放在必须参数前后都可;
  • TypeScript 会识别默认参数的类型,如果给这个带默认值的参数传了别的类型的参数则会报错。

# 剩余参数

如果我们定义一个函数,这个函数可以输入任意个数的参数,那么我们就无法在定义参数列表的时候挨个定义。在 ES6 发布之前,我们需要用到 arguments 来获取参数列表。

特点:

  • 每一个函数都包含的一个类数组对象;
  • 它包含在函数调用时传入函数的所有实际参数(简称实参);
  • 它还包含一个 length 属性,记录参数个数。
// javascript
function handleData() {
  if (arguments.length === 1) return arguments[0] * 2;
  else if (arguments.length === 2) return arguments[0] * arguments[1];
  else return Array.prototype.slice.apply(arguments).join("_");
}
handleData(2); // 4
handleData(2, 3); // 6
handleData(1, 2, 3, 4, 5); // '1_2_3_4_5'
// 这段代码如果在TypeScript环境中,三个对handleData函数的调用都会报错,因为handleData函数定义的时候没有参数。

重要:

在 ES6 中,加入了"…"拓展运算符,它可以将一个数组、对象进行拆解。

它还支持用在函数的参数列表中,用来处理任意数量的参数:

const handleData = (arg1, ...args) => {
  // 这里省略逻辑
  console.log(args);
};
handleData(1, 2, 3, 4, 5); // [ 2, 3, 4, 5 ]

可以看到,args 是除了 arg1 之外的所有实参的集合,它是一个数组。举例:

const handleData = (arg1: number, ...args: number[]) => {
  //
};
handleData(1, "a"); // error 类型"string"的参数不能赋给类型"number"的参数

# 函数重载

TS函数重载区别于其他语言中的重载,TypeScript中的重载是为了针对不同参数个数和类型,推断返回值类型。

在其他一些强类型语言中,函数重载是指定义几个函数名相同,但参数个数或类型不同的函数,在调用时传入不同的参数,编译器会自动调用适合的函数。

TypeScript的函数重载通过为一个函数指定多个函数类型定义,从而对函数调用的返回值进行检查。如下:

// 这个是重载的一部分,指定当参数类型为string时,返回值为string类型的元素构成的数组
function handleData(x: string): string[];
// 这个也是重载的一部分,指定当参数类型为number时,返回值类型为string
function handleData(x: number): string; 

// 这个就是重载的内容了,这是实体函数,不算做重载的部分
function handleData(x: any): any { 
  if (typeof x === "string") {
    return x.split("");
  } else {
    return x
      .toString()
      .split("")
      .join("_");
  }
}

handleData("abc").join("_");
handleData(123).join("_"); // error 类型"string"上不存在属性"join"
handleData(false); // error 类型"boolean"的参数不能赋给类型"number"的参数。

这三个定义组成了一个函数——完整的带有类型定义的函数,前两个function定义的就称为函数重载。

重载只能用 function 来定义,不能使用接口、类型别名等。