在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression)
函数有输入和输出,在typescript中可以对他们进行约束
函数声明
function add(a: number, b: number):number {
return a + b;
}
注意:输入多余或者少于函数的定义都是不允许的
function add(a: number, b: number):number {
return a + b;
}
// 少于定义
add(1);
// semantic error TS2554: Expected 2 arguments, but got 1.
// 多余定义
add(1, 2, 3);
// semantic error TS2554: Expected 2 arguments, but got 3.
如果函数定义时没有明确定义形参的类型,那么在调用函数的时候ts的类型推导会自动推导出形参的类型
function add(a, b: number):number {
return a + b;
}
add(1, 2);
此时形参a的类型,默认被推导为number
函数表达式
给变量add赋值一个函数
let add = (a: number, b: number):number => {
return a + b;
}
此时编译并不会报任何问题,编译后的结果也可以正常运行,但是,我们通常需要给变量add增加一个类型校验,以增加代码的安全性和可靠性
let add:(a:number, b:number) => number = (a: number, b: number) => {
return a + b;
}
此时代码看起来过于臃肿也不便于我们阅读和理解,我们可以把对变量类型的定义单独提取出来
let add:(a:number, b:number) => number;
add = (a: number, b: number) => {
return a + b;
}
也可以使用type关键字把类型提取出来
type ITypeAdd = (a:number, b:number) => number;
let add: ITypeAdd = (a: number, b: number) => {
return a + b;
}
函数在本质上是一个对象,特殊的地方在于,函数是可调用的对象。因此,可以使用对象类型来表示函数类型。所以我们还可以写成下面这样:
let add: {
(a:number, b:number): number
};
add = (a: number, b: number) => {
return a + b;
}
使用对象来表示函数的类型的优势在于,如果add除了在可以调用的情况下,我们给它新增了个属性 version;
function add(a: number, b: number): number {
return a + b;
}
add.version = '1.0.0';
// 函数类型字面量,无法准确描述add的类型
let add1: ((a:number, b:number)=> number) = add;
console.log(add1.vertions);
// semantic error TS2339: Property 'vertions' does not exist on type '(a: number, b: number) => number'
// 使用对象字面量可以准确描述函数add的类型
let add2: {(a:number, b:number): number, version: string} = add;
console.log(add2.version); // 1.0.0
函数中的this
在ts如果this没有指定this值的类型话,默认会设置为any类型,但是any类型对类型检查没有任何的帮助,因此ts提供提供了一个'--noImplicitThis'的编译选项,如果开启了该编译选项时,如果this值获得了any类型,那么将产生变异错误,如果函数中没有引用this值没有任何影响,比如:
// 没有任何影响
function add() {
const a = 0;
}
// 编译报错
function add() {
this.a; // 'this' implicitly has type 'any' because it does not have a type annotation.
}
函数中的this,可以通过一个特殊的this参数来定义
function add(this:{a: number, b: number}, keys:'a' | 'b') {
console.log(this.a);
}
add.call({a: 1, b: 2}, 'a');
此时在调用this的时候不会报任何错误,同时ide也会把所有的变量都索引提示,但是这样写过于啰嗦,我们可以把this类型和keys的类型单独提取出来
let obj = {a: 1, b: 2};
// 获取对象的类型
type objType = typeof obj;
// 获取类型中的key
type keysType = keyof objType;
function add(this:objType, keys:keysType) {
console.log(this.a);
}
add.call(obj, 'a');
可选参数
在js中,每一个参数都是可选参数,在ts中默认所有的参数都是必填的,在形参后面增加一个?就可以将该参数变为可选参数
function add(a:number, b?:number) {
console.log(a, b);
}
add(1);
可选参数必须位于函数参数列表的末尾位置,在可选参数后面再出现必选参数,编译时会报错
function add(a?:number, b:number) {
console.log(a, b);
}
// A required parameter cannot follow an optional parameter.
如果在函数的最后参数有赋默认值,那么这参数也会变为可选参数
function add(a:number, b:number = 2) {
console.log(a, b);
}
add(1)
?和=不能同时使用,否组会报错
function add(a:number, b?:number = 2) {
console.log(a, b);
}
// Parameter cannot have question mark and initializer.
函数的重载
在工作场景中,我们经常会遇到函数的返回值是根据参数来确认的,比如下面的例子,如果我传入的是一个数字的话,就返回数字1,如果我传入的是一个字符串的话,就返回一个字符串a,我们可能会这样写:
function add(a:number | string): number | string {
if (typeof a === 'number') {
return 1;
}
else{
return 'a';
}
}
let num = add(5);
// num: string | number
此时呢,我们发现,变量num也就是函数的返回值是 string | number,并没有像我们想象的那样,应该返回一个数字类型。此时我们就需要用到重载。
不带有函数体的函数声明语句叫做函数重载,它只提供函数的类型信息
function add(a: number): number;
function add(a: string): string;
// 此时函数体的参数和返回值的类型,可以用any代替
function add(a:any): any {
if (typeof a === 'number') {
return 1;
}
else{
return 'a';
}
}
let num = add(1);
// num: number
let num = add('abc');
// num: string
函数重载以后,函数的返回值类型就和我们的要求一致了,此时,传入数字,函数返回的是数字类型,传入字符串,函数返回的是字符串类型。
a