graph TD
A[函数重载] --> B[声明多个类型签名]
A --> C[定义实现函数]
B --> D[精确表达参数类型组合]
C --> E[处理所有签名情况]
A --> F[编译器类型检查]
F --> G[调用点自动匹配签名]
F --> H[提供准确类型推断]
A --> I[应用场景]
I --> J[REST API处理]
I --> K[DOM操作]
I --> L[数学运算]
I --> M[输入验证器]
在 TypeScript 中,函数重载(Function Overloading)是一种强大的类型系统特性,它允许你为单一函数定义多个类型签名,从而精确描述函数在不同参数组合下的行为。这种能力使你能够创建出类型安全且接口优雅的多态函数。
为什么需要函数重载?
考虑一个常见需求:实现加法函数,既能处理数字,也能处理字符串连接:
// 简单但不够精确的实现
function add(a: number | string, b: number | string): number | string {
if (typeof a === "number" && typeof b === "number") {
return a + b;
}
return a.toString() + b.toString();
}
const result1 = add(2, 3); // 期望是number,实际类型为 number | string
const result2 = add("TS", "4.9"); // 期望是string,实际类型为 number | string
这里的问题在于返回值类型的不确定性。函数重载解决了这个问题,提供精确的类型推断:
// 使用函数重载
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any {
if (typeof a === "number" && typeof b === "number") {
return a + b;
}
return a.toString() + b.toString();
}
const numResult = add(2, 3); // number
const strResult = add("TS", "4.9"); // string
函数重载的完整语法结构
函数重载包含两部分:类型签名(重载签名) 和 实现函数
// 重载签名(类型声明)
function functionName(param1: Type1): ReturnType1;
function functionName(param1: Type2, param2: Type3): ReturnType2;
function functionName(param1: Type4, param2?: Type5): ReturnType3;
// 实现函数(包含实际逻辑)
function functionName(param1: unknown, param2?: unknown): unknown {
// 实际函数实现
}
关键规则:
- 签名顺序很重要:编译器从上到下匹配签名
- 实现函数不可直接调用:只能通过匹配的重载签名调用
- 参数类型必须兼容:实现函数的参数类型必须包含所有重载签名的可能类型
- 返回值类型必须一致:实现函数的返回值必须是所有重载签名返回值的超集
函数重载的四种应用模式
1. 参数数量不同
function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(yearOrTimestamp: number, month?: number, day?: number): Date {
if (month !== undefined && day !== undefined) {
return new Date(yearOrTimestamp, month, day);
}
return new Date(yearOrTimestamp);
}
const d1 = createDate(1609459200000); // 使用时间戳
const d2 = createDate(2023, 0, 1); // 使用年月日
2. 参数类型不同
interface Circle { kind: "circle"; radius: number; }
interface Rectangle { kind: "rectangle"; width: number; height: number; }
function calculateArea(shape: Circle): number;
function calculateArea(shape: Rectangle): number;
function calculateArea(shape: Circle | Rectangle): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius * shape.radius;
case "rectangle":
return shape.width * shape.height;
}
}
const circleArea = calculateArea({ kind: "circle", radius: 10 }); // 精确返回number
const rectArea = calculateArea({ kind: "rectangle", width: 5, height: 10 });
3. 参数数量与类型组合不同
// 重载1: 字符串搜索,返回索引
function search(source: string, substring: string): number;
// 重载2: 数组搜索,返回元素或undefined
function search<T>(array: T[], predicate: (item: T) => boolean): T | undefined;
// 实现函数
function search(source: any, criteria: any): any {
if (typeof source === "string") {
return source.indexOf(criteria);
}
return source.find(criteria);
}
const index = search("TypeScript", "Script"); // 返回number
const item = search([1, 2, 3], (n) => n > 2); // 返回number | undefined
4. 可选参数与默认值
type Matrix = number[][];
// 重载1: 提供单个值填充整个矩阵
function createMatrix(size: number, defaultValue?: number): Matrix;
// 重载2: 提供值生成函数
function createMatrix(size: number, initFn: (row: number, col: number) => number): Matrix;
// 实现函数
function createMatrix(size: number, init: any = 0): Matrix {
const matrix: Matrix = [];
// 处理函数生成器
if (typeof init === "function") {
for (let i = 0; i < size; i++) {
matrix[i] = [];
for (let j = 0; j < size; j++) {
matrix[i][j] = init(i, j);
}
}
} else {
// 处理默认值
for (let i = 0; i < size; i++) {
matrix[i] = Array(size).fill(init);
}
}
return matrix;
}
// 3x3 矩阵,元素全为0
const zeros = createMatrix(3);
// 3x3 矩阵,元素全为1
const ones = createMatrix(3, 1);
// 3x3 对角线为1,其余为0
const identity = createMatrix(3, (row, col) => row === col ? 1 : 0);
实际应用场景
1. REST API 请求封装
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
// 无请求体的情况
function request(url: string, method: "GET"): Promise<Response>;
// 包含请求体的情况
function request(url: string, method: Exclude<HttpMethod, "GET">, body: object): Promise<Response>;
// 实现函数
async function request(url: string, method: HttpMethod, body?: object): Promise<Response> {
const options: RequestInit = { method };
if (body) {
options.headers = { "Content-Type": "application/json" };
options.body = JSON.stringify(body);
}
return fetch(url, options);
}
// 使用示例
const getResponse = await request("/api/users", "GET"); // GET无需body
const postResponse = await request("/api/users", "POST", { name: "Alice" });
const putResponse = await request("/api/users/1", "PUT", { name: "Bob" });
// 错误尝试:给GET提供body
const invalid = await request("/api", "GET", {}); // 报错: Unexpected parameter
2. DOM操作辅助函数
// 重载1: 创建新元素
function dom(tag: string, attributes?: object): HTMLElement;
// 重载2: 选择现有元素
function dom(selector: string, parent?: Element): Element | null;
// 实现函数
function dom(param1: string, param2?: any): Element | HTMLElement | null {
if (param1.startsWith("<") && param1.endsWith(">")) {
// 创建元素模式
const tag = param1.slice(1, -1);
const element = document.createElement(tag);
if (param2) {
Object.entries(param2).forEach(([key, value]) => {
element.setAttribute(key, value as string);
});
}
return element;
} else {
// 查询元素模式
const parent = param2 || document;
return parent.querySelector(param1);
}
}
// 创建元素
const button = dom("<button>", { id: "submit", class: "btn" });
// 查询元素
const form = dom("#loginForm");
3. 数学函数库
// 计算两点距离
function distance(p1: number, p2: number): number; // 坐标轴上的点
function distance(p1: [number, number], p2: [number, number]): number; // 二维点
function distance(p1: [number, number, number], p2: [number, number, number]): number; // 三维点
function distance(p1: any, p2: any): number {
if (typeof p1 === "number" && typeof p2 === "number") {
return Math.abs(p1 - p2);
}
if (Array.isArray(p1) && Array.isArray(p2)) {
if (p1.length === 2 && p2.length === 2) {
// 二维距离
const [x1, y1] = p1;
const [x2, y2] = p2;
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
if (p1.length === 3 && p2.length === 3) {
// 三维距离
const [x1, y1, z1] = p1;
const [x2, y2, z2] = p2;
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2 + (z2 - z1) ** 2);
}
}
throw new Error("Invalid argument types");
}
// 使用示例
const axisDistance = distance(5, 10); // 5
const twoDDistance = distance([0, 0], [3, 4]); // 5
const threeDDistance = distance([1, 2, 3], [4, 6, 9]); // sqrt(50) ≈ 7.07
函数重载的陷阱与解决方案
1. 签名顺序错误
问题实例:
function example(x: any): number;
function example(x: string): string;
function example(x: any): any {
return x;
}
const result = example("hello"); // 返回类型为number,但应该是string
解决方案: 将更具体的签名放在前面
function example(x: string): string;
function example(x: any): number;
function example(x: any): any {
return x;
}
2. 忽略可选参数和剩余参数
问题实例:
function log(message: string, userId?: number): void;
function log(message: string): void {
console.log(message, userId || "Anonymous");
}
log("User logged in", 123); // 错误: 参数数量不匹配
解决方案: 在实现中正确处理可选参数
function log(message: string, userId?: number): void;
function log(message: string, userId?: number): void {
console.log(message, userId || "Anonymous");
}
3. 无法区分参数类型
问题实例:
function merge(
a: string | number,
b: string | number
): string | number {
if (typeof a === "string" && typeof b === "string") {
return a + b;
}
if (typeof a === "number" && typeof b === "number") {
return a + b;
}
throw new Error("Invalid types");
}
解决方案: 使用函数重载提供精确类型
function merge(a: string, b: string): string;
function merge(a: number, b: number): number;
function merge(
a: string | number,
b: string | number
): string | number {
// 实现同上 (但调用时类型安全)
}
进阶技巧:结合泛型与重载
将函数重载与泛型结合,创建强大且类型安全的API:
类型安全的事件处理系统
// 事件映射类型
type EventMap = {
click: { x: number; y: number };
keydown: { key: string; code: number };
error: Error;
};
// 重载签名
function trigger<T extends keyof EventMap>(
eventName: T,
eventData: EventMap[T]
): void;
function trigger(eventName: string, eventData?: unknown): void;
// 实现函数
function trigger(eventName: any, eventData?: any): void {
console.log(`Triggering ${eventName}`, eventData);
// 实际触发事件逻辑...
}
// 类型安全的使用
trigger("click", { x: 10, y: 20 }); // 正确
trigger("keydown", { key: "Enter", code: 13 }); // 正确
trigger("click", { key: "A" }); // 错误: 缺少x,y属性
trigger("scroll", { delta: 100 }); // 使用字符串重载 - 允许未知事件
链式方法重载
适用于构建流畅API接口:
class QueryBuilder {
// 重载1: 设置整数值
where(key: string, value: number): this;
// 重载2: 设置布尔值
where(key: string, value: boolean): this;
// 重载3: 设置字符串值
where(key: string, value: string): this;
// 重载4: 设置范围条件
where(key: string, min: number, max: number): this;
// 实现函数
where(key: string, valueOrMin: any, max?: number): this {
if (max !== undefined) {
console.log(`Adding range condition: ${key} BETWEEN ${valueOrMin} AND ${max}`);
} else {
console.log(`Adding condition: ${key} = ${valueOrMin}`);
}
return this; // 返回this支持链式调用
}
}
const query = new QueryBuilder()
.where("age", 25) // 精确值
.where("active", true) // 布尔值
.where("name", "Alice") // 字符串
.where("score", 80, 100); // 范围
函数重载最佳实践
- 保持简单:避免超过4个重载签名,复杂的可考虑重构
- 精确命名:使用表达性强的函数名反映不同重载的行为
- 文档清晰:为每个重载签名添加JSDoc注释
- 一致性优先:保持不同重载的返回类型相似
- 测试覆盖:为每个重载路径编写单元测试
/**
* 处理不同类型的输入,返回格式化信息
*
* @overload 处理数字输入
* @param value - 数值
* @returns 格式化货币字符串
*/
function process(value: number): string;
/**
* @overload 处理日期输入
* @param value - 日期对象
* @returns 格式化日期字符串
*/
function process(value: Date): string;
/**
* @overload 处理自定义对象
* @param value - 用户对象
* @returns 格式化用户信息
*/
function process(value: { name: string; age: number }): string;
function process(value: any): string {
// 统一实现
}
何时避免函数重载?
在某些场景下,其他类型技术可能更合适:
-
参数类型组合过多 → 使用 对象参数 替代
// 不推荐:多个重载 function draw(size: number): void; function draw(width: number, height: number): void; // 推荐:单一签名,对象参数 function draw(options: | { size: number } | { width: number; height: number } ): void; -
返回值类型复杂多变 → 使用 条件类型
// 使用条件类型替代重载 async function fetchData<T>(id: T): Promise< T extends number ? User : T extends string ? Product : never >; -
相似参数不同类型 → 使用 泛型
// 使用泛型替代多个重载 function identity<T>(arg: T): T { return arg; }
函数重载的三大价值
-
提升类型安全性
在编译时确保函数调用的参数和返回值类型正确,避免运行时错误 -
改善开发体验
IDE和编辑器能提供精确的自动补全和文档提示,提高效率 -
增强代码表达能力
清晰地表达函数支持的不同用法模式,自文档化代码