# 函数类型
# 基本用法
简单的定义函数写法:
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 来定义,不能使用接口、类型别名等。