TypeScript重载函数

重载函数是指一个函数同时拥有多个同类的函数签名。例如,一个函数拥有两个及以上的调用签名,或者一个构造函数拥有两个及以上的构造签名。当使用不同数量和类型的参数调用重载函数时,可以执行不同的函数实现代码。

TypeScript中的重载函数与其他编程语言中的重载函数略有不同。

下例中定义了一个重载函数add。它接受两个参数,若两个参数的类型为number,则返回它们的和;若两个参数的类型为数组,则返回合并后的数组。在调用add函数时,允许使用这两个调用签名之一并且能够得到正确的返回值类型。示例如下:

function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];
function add(x: number | any[], y: number | any[]): any {
   if (typeof x === 'number' && typeof y === 'number') {
       return x + y;
   }
   if (Array.isArray(x) && Array.isArray(y)) {
       return [...x, ...y];
   }
}

const a: number = add(1, 2);
const b: number[] = add([1], [2]);

在使用函数声明定义函数时能够定义重载函数。重载函数的定义由以下两部分组成:

  • 一条或多条函数重载语句。
  • 一条函数实现语句。

函数重载

不带有函数体的函数声明语句叫作函数重载。例如,下例中的add函数声明没有函数体,因此它属于函数重载:

function add(x: number, y: number): number;

函数重载的语法中不包含函数体,它只提供了函数的类型信息。函数重载只存在于代码编译阶段,在编译生成JavaScript代码时会被完全删除,因此在最终生成的JavaScript代码中不包含函数重载的代码。

函数重载允许存在一个或多个,但只有多于一个的函数重载才有意义,因为若只有一个函数重载,则可以直接定义函数实现。在函数重载中,不允许使用默认参数。

函数重载应该位于函数实现之前,每一个函数重载中的函数名和函数实现中的函数名必须一致。

下例中第1行和第2行分别定义了两个函数重载,第3行是函数实现。它们具有相同的函数名add,并且每一个函数重载都位于函数实现之前。

function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];
function add(x: number | any[], y: number | any[]): any {
   // 省略了实现代码
}

同时需要注意,在各个函数重载语句之间以及函数重载语句与函数实现语句之间不允许出现任何其他语句,否则将产生编译错误。示例如下:

function add(x: number, y: number): number;

const a = 0; // <-- 编译错误

function add(x: any[], y: any[]): any[];

const b = 0; // <-- 编译错误

function add(x: number | any[], y: number | any[]): any {
   // 省略了实现代码
}

函数实现

函数实现包含了实际的函数体代码,该代码不仅在编译时存在,在编译生成的JavaScript代码中同样存在。每一个重载函数只允许有一个函数实现,并且它必须位于所有函数重载语句之后,否则将产生编译错误。

function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];

// 函数实现必须位于最后
function add(x: number | any[], y: number | any[]): any {
   // 省略了实现代码
}

TypeScript中的重载函数最令人迷惑的地方在于,函数实现中的函数签名不属于重载函数的调用签名之一,只有函数重载中的函数签名能够作为重载函数的调用签名。

下例中的add函数只有两个调用签名,分别为第1行与第2行定义的两个重载签名,而第3行函数实现中的函数签名不是add函数的调用签名。

function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];
function add(x: number | any[], y: number | any[]): any {
   // 省略了实现代码
}

因此,我们可以使用两个number类型的值来调用add函数,或者使用两个数组类型的值来调用add函数。但是,不允许使用一个number类型和一个数组类型的值来调用add函数,尽管在函数实现的函数签名中允许这种调用方式。示例如下:

// 正确的调用方式
add(1, 2);
add([1], [2]);

// 错误的调用方式
add(1, [2]);
add([1], 2);

函数实现需要兼容每个函数重载中的函数签名,函数实现的函数签名类型必须能够赋值给函数重载的函数签名类型。示例如下:

function foo(x: number): boolean;
//       ~~~
//       编译错误:重载签名与实现签名的返回值类型不匹配
function foo(x: string): void;
//       ~~~
//       编译错误:重载签名与实现签名的参数类型不匹配
function foo(x: number): void {
    // 省略函数体代码
}

此例中,重载函数foo可能的参数类型为number类型或string类型,同时返回值类型可能为boolean类型或void类型。因此,在函数实现中的参数x必须同时兼容number类型和string类型,而返回值类型则需要兼容boolean类型和void类型。我们可以使用联合类型来解决这些问题,示例如下:

function foo(x: number): boolean;
function foo(x: string): void;
function foo(x: number | string): any {
   // 省略函数体代码
}

在其他一些编程语言中允许存在多个函数实现,并且在调用重载函数时编程语言负责选择合适的函数实现执行。在TypeScript中,重载函数只存在一个函数实现,开发者需要在这个唯一的函数实现中实现所有函数重载的功能。这就需要开发者自行去检测参数的类型及数量,并根据判断结果去执行不同的操作。示例如下:

function add(x: number, y: number): number;
function add(x: any[], y: any[]): any[];
function add(x: number | any[], y: number | any[]): any {
   if (typeof x === 'number' && typeof y === 'number') {
       return x + y;
   }

   if (Array.isArray(x) && Array.isArray(y)) {
       return [...x, ...y];
   }
}

TypeScript不支持为不同的函数重载分别定义不同的函数实现。从这点上来看,TypeScript中的函数重载不是特别便利。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!