Skip to content

函数的类型签名

函数的类型就是描述了函数入参类型与函数返回值类型,它们同样使用:的语法进行类型标注。

ts
// 声明式
function add(x, y) {
  return x + y;
}
// TS
function add(x: number, y: number): number {
  return x + y;
}

// 表达式
// Anonymous function
let myAdd = function (x, y) {
  return x + y;
};
// ==>  TS
let myAdd = function (x: number, y: number): number {
  return x + y;
};

我们可以给每个参数添加类型之后再为函数本身添加返回值类型。 TypeScript 能够根据返回语句自动推断出返回值类型,因此我们通常省略它。 我们也可以像对变量进行类型标注那样,对 myAdd 这个变量进行类型声明:

ts
let myAdd: (x: number, y: number) => number = function (x, y) {
  return x + y;
};

// (x: number, y: number) => number 函数类型签名
// => 箭头函数后面的是函数的返回值类型

返回值类型是函数类型的必要部分,如果函数没有返回任何值,也必须指定返回值类型为 void 而不能留空。 箭头函数

ts
// 方式一  表达式的形式声明
const foo = (name: string): number => {
  return name.length;
};

// 方式二 变量名标注函数签名 (可读性差)
const foo: (name: string) => number = (name) => {
  return name.length;
};

方式二的声明方式中,当函数类型声明混合箭头函数声明时,代码的可读性会非常差。因此,一般不推荐这么使用,要么直接在函数中进行参数和返回值的类型声明,要么使用类型别名将函数声明抽离出来

类型别名 Type

ts
// 抽离函数声明
type FuncFoo = (name: string) => number;

const foo: FuncFoo = (name) => {
  return name.length;
};

接口描述函数

如果只是为了描述这个函数的类型结构,我们甚至可以使用 interface 来进行函数声明

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

let mySearch: SearchFunc;
// 对于函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配
mySearch = function (src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
};

Void 类型

在 TypeScript 中,一个没有返回值(即没有调用 return 语句)的函数,其返回类型应当被标记为 void 而不是 undefined,即使它实际的值是 undefined。

ts
// 没有调用 return 语句
function foo(): void {}

// 调用了 return 语句,但没有返回值
function bar(): void {
  return;
}

在 TypeScript 中,undefined 类型是一个实际的、有意义的类型值,而 void 才代表着空的、没有意义的类型值。 相比之下,void 类型就像是 JavaScript 中的 null 一样。因此在我们没有实际返回值时,使用 void 类型能更好地说明这个函数没有进行返回操作

函数参数

可选参数

TypeScript 里的每个函数参数都是必须的。传递给一个函数的参数个数必须与函数期望的参数个数一致。

ts
function buildName(firstName: string, lastName: string) {
  return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right

在 TypeScript 里我们可以在参数名旁使用 ?实现可选参数的功能

ts
function buildName(firstName: string, lastName?: string) {
  if (lastName) return firstName + " " + lastName;
  else return firstName;
}

let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right

**eg2 :注意:可选参数必须位于必选参数之后,**因为在 JavaScript 中函数的入参是按照位置(形参),而不是按照参数名(名参)进行传递。

默认参数

我们也可以直接将可选参数与默认值合并,但此时就不能够使用 ? 了,因为既然都有默认值,那肯定是可选参数啦。

ts
// 在函数逻辑中注入可选参数默认值
function foo1(name: string, age?: number): number {
  // ?? TS空值合并操作
  // 如果age为null或undefined;表达式值为18
  const inputAge = age ?? 18;
  return name.length + inputAge;
}

// 直接为可选参数声明默认值
function foo2(name: string, age: number = 18): number {
  const inputAge = age;
  return name.length + inputAge;
}

带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined 值来获得默认值。

ts
function buildName(firstName = "Will", lastName: string) {
  return firstName + " " + lastName;
}

let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams"

rest 剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 而剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。由于其实际上是一个数组,这里我们也应当使用数组类型进行标注

ts
function foo(arg1: string, ...rest: any[]) {}
ts
function buildName(firstName: string, ...restOfName: string[]) {
  return `${firstName} ${restOfName.join(" ")}`;
}

// let Fun: (fname: string, ...rest: string[]) => string = buildName;

元组类型标注

ts
function foo(arg1: string, ...rest: [number, boolean]) {}

foo("linbudu", 18, true);

重载

在某些逻辑较复杂的情况下,函数可能有多组入参类型和返回值类型:

ts
function func(foo: number, bar?: boolean): string | number {
  if (bar) {
    return String(foo);
  } else {
    return foo * 599;
  }
}

要想实现与入参关联的返回值类型,我们可以使用 TypeScript 提供的函数重载签名(Overload Signature,将以上的例子使用重载改写:

ts
function func(foo: number, bar: true): string;
function func(foo: number, bar?: false): number;
function func(foo: number, bar?: boolean): string | number {
  if (bar) {
    return String(foo);
  } else {
    return foo * 599;
  }
}

const res1 = func(599); // number
const res2 = func(599, true); // string
const res3 = func(599, false); // number

这里我们的三个 function func 其实具有不同的意义:

  • function func(foo: number, bar: true): string,重载签名一,传入 bar 的值为 true 时,函数返回值为 string 类型。
  • function func(foo: number, bar?: false): number,重载签名二,不传入 bar,或传入 bar 的值为 false 时,函数返回值为 number 类型。
  • function func(foo: number, bar?: boolean): string | number,函数的实现签名,会包含重载签名的所有可能情况。

注意:拥有多个重载声明的函数在被调用时,是按照重载的声明顺序往下查找的。 因此在第一个重载声明中,为了与逻辑中保持一致,即在 bar 为 true 时返回 string 类型,这里我们需要将第一个重载声明的 bar 声明为必选的字面量类型。 实际上,TypeScript 中的重载更像是伪重载,它只有一个具体实现,其重载体现在方法调用的签名上而非具体实现上。而在如 C++ 等语言中,重载体现在多个名称一致但入参不同的函数实现上,这才是更广义上的函数重载。

异步函数签名

对于异步函数、Generator 函数、异步 Generator 函数的类型签名,其参数签名基本一致,而返回值类型则稍微有些区别:

ts
async function asyncFunc(): Promise<void> {}

// Generator 函数与异步 Generator 函数 基本不使用了 了解
function* genFunc(): Iterable<void> {}

async function* asyncGenFunc(): AsyncIterable<void> {}

对于异步函数(即标记为 async 的函数),其返回值必定为一个 Promise 类型,而 Promise 内部包含的类型则通过泛型的形式书写,即 Promise<T>