Skip to content

函数重载

一些 JavaScript 函数可以在不同的参数数量和类型中被调用。例如,你可能会写一个函数来产生一个 Date,它需要一个时间戳(一个参数)或一个月/日/年规格(三个参数)。

在 TypeScript 中,我们可以通过编写重载签名来指定一个可以以不同方式调用的函数。要做到这一点,要写一些数量的函数签名(通常是两个或更多),然后是函数的主体:

typescript
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
  if (d !== undefined && y !== undefined) {
    return new Date(y, mOrTimestamp, d);
  } else {
    return new Date(mOrTimestamp);
  }
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);

在这个例子中,我们写了两个重载:一个接受一个参数,另一个接受三个参数。这前两个签名被称为重载签名。

然后,我们写了一个具有兼容签名的函数实现。函数有一个实现签名,但这个签名不能被直接调用。即使我们写了一个在所需参数之后有两个可选参数的函数,它也不能以两个参数被调用!

重载签名和实现签名

这是一个常见的混乱来源。通常我们会写这样的代码,却不明白为什么会出现错误:

typescript
function fn(x: string): void;
function fn() {
  // ...
}
// 期望能够以零参数调用
fn();

同样,用于编写函数体的签名不能从外面 "看到"。

实现的签名从外面是看不到的。在编写重载函数时,你应该总是在函数的实现上面有两个或多个签名。

实现签名也必须与重载签名兼容。 例如,这些函数有错误,因为实现签名没有以正确的方式匹配重载:

typescript
function fun(x: boolean): void;
// 参数类型不正确
function fun(x: string): void;
function fun(x: boolean) {}
typescript
function fun(x: string): string;
// 返回类型不正确
function fun(x: number): boolean;
function fun(x: string | number) {
  return "oops";
}

编写好的重载

和泛型一样,在使用函数重载时,有一些准则是你应该遵循的。遵循这些原则将使你的函数更容易调用,更容易理解,更容易实现。

让我们考虑一个返回字符串或数组长度的函数:

typescript
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any) {
  return x.length;
}

这个函数是好的;我们可以用字符串或数组来调用它。然而,我们不能用一个可能是字符串或数组的值来调用它,因为 TypeScript 只能将一个函数调用解析为一个重载

typescript
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);

因为两个重载都有相同的参数数量和相同的返回类型,我们可以改写一个非重载版本的函数:

typescript
function len(x: any[] | string) {
  return x.length;
}

len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]); // OK

这就好得多了! 调用者可以用任何一种值来调用它,而且作为额外的奖励,我们不需要找出一个正确的实现签名。

在可能的情况下,总是倾向于使用联合类型的参数而不是重载参数

函数内 This 的声明

TypeScript 会通过代码流分析来推断函数中的 this 应该是什么,比如下面的例子:

typescript
const user = {
  id: 123,
  admin: false,
  becomeAdmin: function () {
    this.admin = true;
  },
};

TypeScript 理解函数 user.becomeAdmin 有一个对应的 this ,它是外部对象 user 。这个对于很多情况来说已经足够了,但是有很多情况下你需要更多的控制 this 代表什么对象。JavaScript 规范规定,你不能有一个叫 this 的参数,所以 TypeScript 使用这个语法空间,让你在函数体中声明 this 的类型。

typescript
interface User {
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const db: DB = {
  filterUsers: (filter: (this: User) => boolean) => {
    let user1 = {
      admin: true,
    };
    let user2 = {
      admin: false,
    };
    return [user1, user2];
  },
};

const admins = db.filterUsers(function (this: User) {
  return this.admin;
});

这种模式在回调式 API 中很常见,另一个对象通常控制你的函数何时被调用。注意,你需要使用函数而不是箭头函数来获得这种行为。

typescript
interface User {
  admin: boolean;
}

interface DB {
  filterUsers(filter: (this: User) => boolean): User[];
}

const db: DB = {
  filterUsers: (filter: (this: User) => boolean) => {
    let user1 = { admin: true };
    let user2 = { admin: false };
    return [user1, user2];
  },
};
// 不能为箭头函数
const admins = db.filterUsers(() => this.admin);