函数重载
概述
函数重载(Function Overloads)允许一个函数根据不同的参数类型或数量返回不同的类型。通过提供多个函数签名,TypeScript 可以根据调用时传入的参数类型自动选择最匹配的重载签名。函数重载让函数接口更加清晰,能够为不同的使用场景提供精确的类型检查,是 TypeScript 类型系统的重要特性之一。
基本语法
函数重载由两部分组成:
- 重载签名(Overload Signatures):定义函数的不同调用方式,只包含类型信息,不包含实现
- 实现签名(Implementation Signature):包含实际的函数实现,必须兼容所有重载签名
typescript
// 重载签名 1
function process(value: string): string;
// 重载签名 2
function process(value: number): number;
// 实现签名(必须兼容所有重载签名)
function process(value: string | number): string | number {
if (typeof value === 'string') {
return value.toUpperCase();
}
return value * 2;
}
// 使用示例
const str = process("hello"); // 类型为 string
const num = process(42); // 类型为 number重载签名规则
重载签名的顺序
重载签名必须按照从最具体到最通用的顺序排列,TypeScript 会从上到下匹配重载签名:
typescript
// ✅ 正确:从具体到通用
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;
function format(value: string | number | boolean): string {
return String(value);
}
// ❌ 错误:通用签名在前,具体签名无法匹配
function badFormat(value: string | number | boolean): string;
function badFormat(value: string): string; // 这个重载永远不会被匹配
function badFormat(value: string | number | boolean): string {
return String(value);
}重载签名的数量限制
虽然理论上可以有任意数量的重载签名,但建议控制在 5-10 个以内,过多的重载会让代码难以维护:
typescript
// 多个重载签名示例
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'span'): HTMLSpanElement;
function createElement(tag: 'input'): HTMLInputElement;
function createElement(tag: 'button'): HTMLButtonElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag) as HTMLElement;
}使用示例
示例 1:基础函数重载
根据参数类型返回不同的类型:
typescript
// 重载签名:处理字符串
function reverse(value: string): string;
// 重载签名:处理数组
function reverse<T>(value: T[]): T[];
// 实现签名
function reverse<T>(value: string | T[]): string | T[] {
if (typeof value === 'string') {
return value.split('').reverse().join('');
}
return [...value].reverse();
}
// 使用示例
const reversedStr = reverse("hello"); // 类型为 string: "olleh"
const reversedArr = reverse([1, 2, 3, 4]); // 类型为 number[]: [4, 3, 2, 1]
const reversedStrArr = reverse(["a", "b"]); // 类型为 string[]: ["b", "a"]示例 2:参数数量重载
根据参数数量提供不同的行为:
typescript
// 重载签名 1:无参数,返回当前时间戳
function getTimestamp(): number;
// 重载签名 2:传入日期字符串,转换为时间戳
function getTimestamp(dateString: string): number;
// 重载签名 3:传入 Date 对象,转换为时间戳
function getTimestamp(date: Date): number;
// 实现签名
function getTimestamp(dateOrString?: string | Date): number {
if (dateOrString === undefined) {
return Date.now();
}
if (typeof dateOrString === 'string') {
return new Date(dateOrString).getTime();
}
return dateOrString.getTime();
}
// 使用示例
const now = getTimestamp(); // 类型为 number
const fromString = getTimestamp("2024-01-01"); // 类型为 number
const fromDate = getTimestamp(new Date()); // 类型为 number示例 3:复杂类型重载
处理不同对象类型,返回对应的处理结果:
typescript
// 定义类型
interface User {
id: number;
name: string;
}
interface Product {
id: number;
title: string;
price: number;
}
// 重载签名 1:处理用户
function processEntity(entity: User): { userId: number; userName: string };
// 重载签名 2:处理产品
function processEntity(entity: Product): { productId: number; productTitle: string };
// 实现签名
function processEntity(entity: User | Product):
| { userId: number; userName: string }
| { productId: number; productTitle: string } {
if ('name' in entity) {
// 类型守卫:确定是 User
return {
userId: entity.id,
userName: entity.name
};
} else {
// 确定是 Product
return {
productId: entity.id,
productTitle: entity.title
};
}
}
// 使用示例
const user: User = { id: 1, name: "Alice" };
const product: Product = { id: 1, title: "Book", price: 29.99 };
const userResult = processEntity(user); // 类型为 { userId: number; userName: string }
const productResult = processEntity(product); // 类型为 { productId: number; productTitle: string }示例 4:API 请求函数重载
模拟 API 请求,根据不同的参数返回不同的类型:
typescript
// 重载签名 1:GET 请求,返回数据
function apiRequest(url: string): Promise<any>;
// 重载签名 2:POST 请求,传入数据,返回结果
function apiRequest(url: string, method: 'POST', data: object): Promise<any>;
// 重载签名 3:PUT 请求,传入数据,返回结果
function apiRequest(url: string, method: 'PUT', data: object): Promise<any>;
// 重载签名 4:DELETE 请求,返回确认
function apiRequest(url: string, method: 'DELETE'): Promise<{ success: boolean }>;
// 实现签名
function apiRequest(
url: string,
method?: 'GET' | 'POST' | 'PUT' | 'DELETE',
data?: object
): Promise<any> {
// 模拟 API 请求实现
return Promise.resolve({ success: true, data });
}
// 使用示例
const getResult = apiRequest('/api/users'); // Promise<any>
const postResult = apiRequest('/api/users', 'POST', { name: 'Alice' }); // Promise<any>
const putResult = apiRequest('/api/users/1', 'PUT', { name: 'Bob' }); // Promise<any>
const deleteResult = apiRequest('/api/users/1', 'DELETE'); // Promise<{ success: boolean }>示例 5:数组查找函数重载
根据是否传入回调函数,返回不同的结果:
typescript
// 重载签名 1:传入值,返回找到的元素或 undefined
function find<T>(array: T[], value: T): T | undefined;
// 重载签名 2:传入回调函数,返回找到的元素或 undefined
function find<T>(array: T[], predicate: (item: T) => boolean): T | undefined;
// 实现签名
function find<T>(
array: T[],
valueOrPredicate: T | ((item: T) => boolean)
): T | undefined {
if (typeof valueOrPredicate === 'function') {
return array.find(valueOrPredicate);
}
return array.find(item => item === valueOrPredicate);
}
// 使用示例
const numbers = [1, 2, 3, 4, 5];
// 使用值查找
const found1 = find(numbers, 3); // 类型为 number | undefined
// 使用回调函数查找
const found2 = find(numbers, n => n > 3); // 类型为 number | undefined
const found3 = find(numbers, n => n % 2 === 0); // 类型为 number | undefined重载解析规则
TypeScript 在调用重载函数时,会按照以下规则选择最匹配的重载签名:
- 精确匹配优先:优先选择参数类型完全匹配的重载
- 从上到下匹配:按照重载签名的声明顺序依次匹配
- 兼容性匹配:如果找不到精确匹配,选择兼容的重载(如果存在)
typescript
// 重载签名顺序很重要
function example(value: string): string; // 最具体
function example(value: string | number): string; // 较通用
function example(value: any): string; // 最通用
// 实现签名
function example(value: any): string {
return String(value);
}
// 调用示例
example("hello"); // 匹配第一个重载(精确匹配)
example(42); // 匹配第二个重载(兼容匹配)
example(true); // 匹配第三个重载(any 匹配)常见错误和解决方案
错误 1:实现签名不兼容重载签名
typescript
// ❌ 错误:实现签名的参数类型不兼容
function badExample(value: string): string;
function badExample(value: number): number;
function badExample(value: boolean): string | number { // Error: 实现签名必须兼容所有重载
return String(value);
}解决方案:
typescript
// ✅ 正确:实现签名兼容所有重载
function goodExample(value: string): string;
function goodExample(value: number): number;
function goodExample(value: string | number): string | number {
if (typeof value === 'string') {
return value;
}
return value;
}错误 2:重载签名顺序错误
typescript
// ❌ 错误:通用签名在前,具体签名无法被匹配
function badOrder(value: any): any;
function badOrder(value: string): string; // 这个重载永远不会被匹配
function badOrder(value: any): any {
return value;
}解决方案:
typescript
// ✅ 正确:从具体到通用
function goodOrder(value: string): string;
function goodOrder(value: any): any;
function goodOrder(value: any): any {
return value;
}错误 3:重载签名返回类型不一致
typescript
// ❌ 错误:相同参数类型但返回类型不同(这在 TypeScript 中是不允许的)
function badOverload(value: string): string;
function badOverload(value: string): number; // Error: 重复的重载签名
function badOverload(value: string): string | number {
return value;
}解决方案:使用联合类型或不同的参数类型:
typescript
// ✅ 方案 1:使用联合返回类型
function goodOverload1(value: string): string | number;
function goodOverload1(value: string): string | number {
return Math.random() > 0.5 ? value : value.length;
}
// ✅ 方案 2:使用不同的参数类型区分
function goodOverload2(value: string, asNumber: true): number;
function goodOverload2(value: string, asNumber?: false): string;
function goodOverload2(value: string, asNumber?: boolean): string | number {
return asNumber ? value.length : value;
}函数重载与联合类型
函数重载和联合类型在某些场景下可以互换,但各有优势:
使用函数重载的场景
typescript
// 优势:可以为不同的参数类型提供精确的返回类型
function process(value: string): string;
function process(value: number): number;
function process(value: string | number): string | number {
return typeof value === 'string' ? value.toUpperCase() : value * 2;
}
const str = process("hello"); // 类型为 string(精确)
const num = process(42); // 类型为 number(精确)使用联合类型的场景
typescript
// 优势:更简洁,适合参数和返回值关系简单的情况
function process(value: string | number): string | number {
return typeof value === 'string' ? value.toUpperCase() : value * 2;
}
const result1 = process("hello"); // 类型为 string | number(不够精确)
const result2 = process(42); // 类型为 string | number(不够精确)提示
- 函数重载:适合需要为不同参数类型提供精确返回类型的场景
- 联合类型:适合参数和返回值关系简单,不需要精确类型的场景
实际应用场景
场景 1:DOM 操作
typescript
// 根据选择器类型返回不同的元素类型
function querySelector(selector: '#app'): HTMLDivElement | null;
function querySelector(selector: 'input'): HTMLInputElement | null;
function querySelector(selector: 'button'): HTMLButtonElement | null;
function querySelector(selector: string): HTMLElement | null {
return document.querySelector(selector);
}
const app = querySelector('#app'); // HTMLDivElement | null
const input = querySelector('input'); // HTMLInputElement | null
const button = querySelector('button'); // HTMLButtonElement | null场景 2:数据验证
typescript
// 根据验证规则返回不同的结果
function validate(value: string, rule: 'email'): boolean;
function validate(value: string, rule: 'phone'): boolean;
function validate(value: number, rule: 'range', min: number, max: number): boolean;
function validate(
value: string | number,
rule: 'email' | 'phone' | 'range',
min?: number,
max?: number
): boolean {
if (rule === 'email') {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value as string);
}
if (rule === 'phone') {
return /^\d{11}$/.test(value as string);
}
if (rule === 'range' && typeof value === 'number' && min !== undefined && max !== undefined) {
return value >= min && value <= max;
}
return false;
}
const isValidEmail = validate('test@example.com', 'email'); // boolean
const isValidPhone = validate('13800138000', 'phone'); // boolean
const isInRange = validate(42, 'range', 0, 100); // boolean场景 3:配置对象处理
typescript
// 根据配置类型返回不同的处理器
interface ApiConfig {
url: string;
method: 'GET' | 'POST';
}
interface DbConfig {
host: string;
port: number;
}
function createHandler(config: ApiConfig): (data: any) => Promise<any>;
function createHandler(config: DbConfig): (query: string) => Promise<any[]>;
function createHandler(config: ApiConfig | DbConfig): (input: any) => Promise<any> {
if ('url' in config) {
// API 处理器
return async (data: any) => {
// 模拟 API 调用
return { success: true, data };
};
} else {
// 数据库处理器
return async (query: string) => {
// 模拟数据库查询
return [];
};
}
}
const apiHandler = createHandler({ url: '/api', method: 'GET' }); // (data: any) => Promise<any>
const dbHandler = createHandler({ host: 'localhost', port: 5432 }); // (query: string) => Promise<any[]>注意事项
提示
- 函数重载可以让函数接口更加清晰,为不同的使用场景提供精确的类型检查
- 重载签名应该按照从最具体到最通用的顺序排列
- 实现签名必须兼容所有重载签名,通常使用联合类型作为参数类型
- 函数重载在编译后会被移除,只保留实现签名
注意
- 重载签名不能有实现体,只有类型信息
- 实现签名必须放在所有重载签名之后
- 重载签名和实现签名在参数名称上可以不同,但类型必须兼容
- 过多的重载签名会让代码难以维护,建议控制在合理范围内
重要
- 函数重载是编译时的特性,不会影响运行时的性能
- 如果重载签名过于复杂,考虑使用联合类型或泛型来简化
- 实现签名中的类型检查是运行时的,需要手动处理类型守卫
信息
函数重载与泛型的区别:
- 函数重载:为不同的参数类型提供不同的返回类型,类型是固定的
- 泛型:使用类型参数,让函数可以处理任意类型,类型是动态的
- 两者可以结合使用,实现更强大的类型系统
相关链接
- 函数类型 - 了解函数类型的基础知识
- 类型守卫 - 学习如何在实现签名中使用类型守卫
- 联合类型 - 了解联合类型的使用
- 泛型 - 学习泛型的使用,可以与函数重载结合
- TypeScript 官方文档 - 函数重载