函数重载
本教程共 80 篇 · 第 19 篇 · 更新于 2026-08-10 · 约 10 分钟阅读
本节目标:学会定义函数重载,理解重载签名和实现签名的区别,掌握重载的顺序规则,以及什么时候该用联合类型而不是重载。
有些函数调用方式不止一种——参数个数可能不同,参数类型也可能不同。JavaScript 的 Date 构造函数就是典型:可以传一个时间戳,也可以传年月日三个参数。TypeScript 用”函数重载”来描述这种情况。
重载签名 vs 实现签名
重载由两部分组成:一组重载签名和一个实现签名。
// 重载签名 1:传时间戳
function makeDate(timestamp: number): Date;
// 重载签名 2:传年月日
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); // ✅ 匹配重载 1
const d2 = makeDate(5, 5, 2025); // ✅ 匹配重载 2
const d3 = makeDate(1, 3); // ❌ 没有匹配的重载
上面的代码有几个关键点:
- 重载签名只写参数和返回值类型,没有函数体。它们决定外部如何调用这个函数。
- 实现签名是真正有函数体的那个,但它对外部不可见——你不能用实现签名的参数组合来调用函数。
- 实现签名必须兼容所有重载签名。
最后一点特别容易踩坑:d3 = makeDate(1, 3) 传了两个参数,按实现签名看 d? 和 y? 都是可选的,好像可以。但不行——因为实现签名对外部不可见,从外部只能看到两个重载签名(1 个参数或 3 个参数)。
Important实现签名只是给 TypeScript 检查函数体用的,不参与外部调用的类型检查。这一点新手经常搞混。
重载的顺序规则
重载签名的顺序很重要。TypeScript 会从上到下依次匹配,找到第一个兼容的签名就停。
function format(value: string): string;
function format(value: number): string;
function format(value: string | number): string {
return String(value);
}
这两个重载参数类型互不相同,所以顺序无所谓。但如果重载之间存在包含关系,规则是具体的放前面,一般的放后面:
// ✅ 正确:具体在前
function describe(value: "admin"): string;
function describe(value: string): string;
function describe(value: string): string {
if (value === "admin") return "管理员";
return "普通用户";
}
describe("admin"); // "管理员"——匹配第一个重载
describe("guest"); // "普通用户"——匹配第二个重载
如果把顺序反过来,“admin” 会被 string 重载匹配走,第一个重载永远用不到——TypeScript 不会报错,但你的类型行为就不对了。
什么时候不需要重载
重载虽然强大,但不是所有场景都适合。如果多个调用方式的参数个数相同、只是参数类型不同,用联合类型往往更好。
来看一个例子——获取字符串或数组的长度:
// ❌ 用重载——没必要
function len(s: string): number;
function len(arr: any[]): number;
function len(x: any): number {
return x.length;
}
len("hello"); // ✅
len([1, 2, 3]); // ✅
len(Math.random() > 0.5 ? "hello" : [1, 2, 3]); // ❌ 联合类型没法匹配
当参数是 string | any[] 这种联合类型时,TypeScript 无法把它分解到某一个重载上,只能报错。但用联合类型就能直接解决:
// ✅ 用联合类型——更简单
function len(x: string | any[]): number {
return x.length;
}
len("hello"); // ✅
len([1, 2, 3]); // ✅
len(Math.random() > 0.5 ? "hello" : [1, 2, 3]); // ✅ 也能用了
那么什么时候必须用重载?当参数个数不同且返回值类型和参数类型强相关的时候。比如:
function getValue(key: string): string | undefined;
function getValue(key: string, defaultValue: string): string;
function getValue(key: string, defaultValue?: string): string | undefined {
const value = storage.get(key);
if (value === undefined) return defaultValue;
return value;
}
const a = getValue("name"); // 类型是 string | undefined
const b = getValue("name", "unknown"); // 类型是 string
这里用重载是合理的——因为传一个参数和传两个参数时,返回值类型不同。TypeScript 会根据调用时传了几个参数,精确推断返回值。用联合类型做不到这一点。
实现签名的返回值不被检查
有个细节很多人不知道:TypeScript 不检查实现签名的返回值是否真的匹配重载签名声明的返回类型。
function getLength(value: string): number;
function getLength(value: any[]): number;
function getLength(value: any): number {
// 实现中不小心返回了字符串,TS 不会报错
return String(value.length); // ⚠️ 实际上返回的是 string,但 TS 不报错
}
这意味着实现签名里的返回值是你自己负责的——TypeScript 假定你最清楚自己在做什么。写实现体时要格外小心,确保返回值的类型确实匹配重载声明。
Note
return String(value.length)实际上返回了string,但调用方看到的是number。这是一个运行时才能发现的 bug。写重载时一定要自己核对实现体的返回值是否正确。
小结
函数重载用来描述”一种函数,多种调用方式”的场景。核心要点:重载签名对外部可见、实现签名不可见、具体在前一般在后、参数类型不同时优先考虑联合类型。重载不是越多越好——能用联合类型解决的问题,就不要引入重载,保持简单。