一、函数类型

在 JavaScript 开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进 行传递)

那么在使用函数的过程中,函数是否也可以有自己的类型呢?

我们可以编写函数类型的表达式(Function Type Expressions),来表示函数类型;

1
2
3
4
5
6
7
function foo() {}

function foo2(fn: () => void) {
fn();
}

foo2(foo);

() => void 表示函数类型

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Func = (n1: number, n2: number) => void;
function calc(num1: number, num2: number, fn: Func) {
console.log(fn(num1, num2));
}
//这么写num1 num2是默认的any类型
function add(num1, num2) {
return num1 + num2;
}
calc(20, 30, add);

//这样写的话num1 num2就是number类型 因为fn是Func类型 Func中定义了参数类型
calc(20, 30, function (n1, n2) {
return n1 * n2;
});

应该能绕明白吧

函数类型解析

在上面的语法中 (num1: number, num2: number) => void,代表的就是一个函数类型:

接收两个参数的函数:num1 和 num2,并且都是 number 类型

并且这个函数是没有返回值的,所以是 void

在某些语言中,可能参数名称 num1 和 num2 是可以省略,但是 TypeScript 是不可以的:

参数的可选类型

我们可以指定某个参数是可选的

这个时候这个参数 y 依然是有类型的,它是什么类型呢? number | undefined

另外可选类型需要在必传参数的后面:

1
2
3
4
5
6
7
8
9
10
//不报错
function print(x: number, y?: number) {
return x + y;
}


//报错
// function print1(x?: number, y: number) {
// return x + y;
// }

默认参数

1
2
3
4
5
6
function print(x: number, y: number = 100) {
return x + y;
}
print(10, 20); //30
print(10, undefined); //110

从 ES6 开始,JavaScript 是支持默认参数的,TypeScript 也是支持默认参数的

这个时候 y 的类型其实是 undefined 和 number 类型的联合。

剩余参数

从 ES6 开始,JavaScript 也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。

1
2
3
4
5
6
7
8
9
10
function sum(...nums: number[]) {
let total = 0;
for (const i of nums) {
total += i;
}
return total;
}
sum(10, 20, 30); //60
console.log(sum(10, 20, 30, 40, 50)); //150

this 类型

1
2
3
4
5
6
7
const info = {
name: 'xxx',
say() {
console.log(this.name);
},
};
info.say(); //xxx

上面的代码是可以正常运行的,也就是 TypeScript 在编译时,认为我们的 this 是可以正确去使用的

TypeScript 认为函数 say 有一个对应的 this 的外部对象 info,所以在使用时,就会把 this 当做该对象。

1
2
3
4
5
6
7
8
function foo() {
console.log(this.name);
}
const info1 = {
name: 'yyy',
foo: foo,
};
info1.foo(); //报错

强调一下,TypeScript 进行类型检测的目的是让我们的代码更加的安全

  • 所以这里对于 foo 的调用来说,我们虽然将其放到了 info 中,通过 info 去调用,this 依然是指向 info 对象的
  • 但是对于 TypeScript 编译器来说,这个代码是非常不安全的,因为我们也有可能直接调用函数,或者通过别的对象来调用函数
  • 所以这里在编译时会报错,要求我们指定 this 类型

指定 this 的类型

1
2
3
4
5
6
7
8
9
10
11
type NameType = {
name: string;
};
function foo(this: NameType) {
console.log(this.name);
}
const info1 = {
name: 'yyy',
foo: foo,
};
info1.foo(); //yyy

函数的重载

TypeScript 中,如果我们编写了一个 add 函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?

我们可能会这样来编写,但是其实是错误的:

那么这个代码应该如何去编写呢?

  • 在 TypeScript 中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用
  • 一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
1
2
3
4
5
6
7
8
9
10
function sum(n1: number, n2: number): number;
function sum(n1: string, n2: string): number;
function sum(n1: string, n2: number): number;

function sum(n1: any, n2: any) {
return n1 + n2;
}
console.log(sum(20, 30)); //50
console.log(sum('abc', 'cba')); //abccba
console.log(sum('abc', 123)); //abc123

在我们调用 sum 的时候,它会根据我们传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名

注意,有实现体的函数,是不能直接被调用的

联合类型和重载

有一个需求:定义一个函数,可以传入字符串或者数组,获取它们的长度。

1
2
3
4
5
6
7
8
9
10
11
12
//联合类型实现
function getLength(n: string | any[]) {
return n.length;
}

//函数重载实现
function getLength1(n: string): number;
function getLength1(n: any[]): number;

function getLength1(n: any) {
return n.length;
}

开发中我们选择使用哪一种呢?

  • 在可能的情况下,尽量选择使用联合类型来实现