一、什么是函数重载?
函数重载的核心思想是:对外声明多种调用方式,对内用一个统一的实现来处理。
一个完整的函数重载包含两个主要部分:
- 重载签名 :定义了函数的各种调用形式,包括参数的类型、数量和返回值的类型。这些签名没有函数体。
- 实现签名 :这是函数 唯一 的实现,它包含函数体。它的签名必须能够 兼容 所有重载签名。
示例: 一个 add
函数,既可以用于数字相加,也可以用于字符串拼接。
typescript
// 1. 重载签名 (Overload Signatures)
function add(x: number, y: number): number;
function add(x: string, y: string): string;
// 2. 实现签名 (Implementation Signature)
function add(x: any, y: any): any {
// 3. 函数体实现
if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
if (typeof x === 'string' && typeof y === 'string') {
return x + y;
}
throw new Error('Invalid arguments');
}
// 调用
const numSum = add(5, 10); // numSum 的类型被推断为 number
console.log(numSum); // 15
const strSum = add('Hello, ', 'World!'); // strSum 的类型被推断为 string
console.log(strSum); // Hello, World!
分析:
- 外部可见性 :当外部代码调用
add
函数时,TypeScript 会看到两个重载签名。它会根据你传入的参数,从上到下查找第一个匹配的签名。 - 内部实现 :实现签名
function add(x: any, y: any): any
对外部是不可见的,你不能用(any, any)
的方式直接调用add
函数(除非强制类型转换)。 - 兼容性 :实现签名必须涵盖所有重载签名。在这里,
x: any, y: any
可以接受(number, number)
和(string, string)
的情况。
二、重载的顺序
函数重载的顺序至关重要,因为 TypeScript 在解析调用时会 按顺序检查。一旦找到匹配的签名,它就会停止搜索。
- 顺序一般是代码中从上而下的顺序。
- 注:在有类型包含关系的情况下一般有小而大,例如:先
number
,再any
typescript
function move(p: Point): void;
function move(p: any): void;
// ... 实现 ...
function move(p: any): void {
// ...
}
move({ x: 1, y: 2 }); // OK, p 的类型被正确推断为 Point
三、常见的几种函数重载的优化方案
1. 联合类型的应用
参数类型不同,但逻辑和返回类型相似
typescript
// 使用重载 (显得冗余)
function printId(id: number): void;
function printId(id: string): void;
function printId(id: number | string): void {
console.log("Your ID is: " + id);
}
// 使用联合类型 (更简洁)
function printIdSimple(id: number | string): void {
console.log("Your ID is: " + id);
}
2. 可选参数 或 默认参数
如果只是参数数量不同,可以使用 可选参数 或 默认参数 。
typescript
// 使用重载
function greet(person: string): string;
function greet(person: string, greeting: string): string;
function greet(person: string, greeting?: string): string {
return `${greeting || "Hello"}, ${person}!`;
}
// 使用可选参数 (更简洁)
function greetSimple(person: string, greeting?: string): string {
return `${greeting || "Hello"}, ${person}!`;
}
3. 泛型
当函数的输入类型和输出类型之间存在一种模式或关联,但具体的类型是可变的,泛型 是最佳选择。
typescript
// 使用重载 (无法穷举所有类型)
function getFirstElement(arr: number[]): number | undefined;
function getFirstElement(arr: string[]): string | undefined;
function getFirstElement(arr: any[]): any | undefined {
return arr[0];
}
// 使用泛型 (终极解决方案)
function getFirstElementGeneric<T>(arr: T[]): T | undefined {
return arr[0];
}
const firstNum = getFirstElementGeneric<number>([1, 2, 3]); // 推断为 number
console.log(firstNum);
const firstStr = getFirstElementGeneric<string>(['a', 'b', 'c']); // 推断为 string
console.log(firstStr);
总结
如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript/TypeScript开发干货