一、函数重载的核心概念
有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。
简单示例:
reverse('abc') // 'cba'
reverse([1, 2, 3]) // [3, 2, 1]
上面示例中,函数reverse()
可以将参数颠倒输出。参数可以是字符串,也可以是数组。
这意味着,该函数内部有处理字符串和数组的两套逻辑,根据参数类型的不同,分别执行对应的逻辑。这就叫“函数重载”。
展开来说,其核心由两部分构成:
- 重载签名(Overload Signatures):定义函数的不同调用形式(参数类型/数量及返回类型),不包含实现。
- 实现签名(Implementation Signature):包含实际逻辑的函数体,需兼容所有重载签名。
示例:
// 重载签名
function reverse(str:string):string;
function reverse(arr:any[]):any[];
// 实现签名
function reverse(stringOrArray:string|any[]
):string|any[] {if (typeof stringOrArray === 'string')return stringOrArray.split('').reverse().join('');elsereturn stringOrArray.slice().reverse();
}
上面示例中,前两行类型声明列举了重载的各种情况。第三行是函数本身的类型声明,它必须与前面已有的重载声明兼容。
二、函数重载的核心规则
- 签名顺序:a:重载签名需在实现签名之前声明,实现签名参数类型需覆盖所有重载可能性(如联合类型或
any
),b:重载的重载签名与函数的实现签名之间,不能有其他代码,否则报错。c:重载声明的排序很重要,因为 TypeScript 是按照顺序进行检查的,一旦发现符合某个类型声明,就不再往下检查了,所以类型最宽的声明应该放在最后面,防止覆盖其他类型声明。function f(x:any):number; function f(x:string): 0|1; function f(x:any):any {// ... } const a:0|1 = f('hi'); // 报错// 该例子,第一行类型声明x:any范围最宽,导致函数f()的调用都会匹配这行声明,无法匹配第二行类型声明,所以最后一行调用就报错了,因为等号两侧类型不匹配,左侧类型是0|1,右侧类型是number。这个函数重载的正确顺序是,第二行类型声明放到第一行的位置。
- 返回类型匹配:各重载签名的返回类型需独立明确,实现签名需兼容所有可能返回类型 。函数重载的每个类型声明之间,以及类型声明与函数实现的类型之间,不能有冲突。
// 报错 function fn(x:boolean):void; function fn(x:string):void; function fn(x:number|string) {console.log(x); }
- 参数类型处理:实现签名通过类型守卫(如
typeof
、instanceof
)区分参数类型逻辑 。
三、典型应用场景
1. 参数类型决定返回类型
// 根据参数类型返回不同结构
function add(x:number,y:number
):number;
function add(x:any[],y:any[]
):any[];
function add(x:number|any[],y:number|any[]
):number|any[] {if (typeof x === 'number' && typeof y === 'number') {return x + y;} else if (Array.isArray(x) && Array.isArray(y)) {return [...x, ...y];}throw new Error('wrong parameters');
}
上面示例中,函数add()
内部使用if
代码块,分别处理参数的两种情况。
- 调用提示:IDE会根据参数类型自动推断返回类型,提升开发体验 。
虽然函数的具体实现里面,有完整的类型声明。但是,函数实际调用的类型,以前面的类型声明为准。比如,上例的函数实现,参数类型和返回值类型都是
number|any[]
,但不意味着参数类型为number
时返回值类型为any[]
。
2. 参数数量不同时的行为差异
// 参数数量不同时的重载
function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(a: number, b?: number, c?: number): Date {if (b !== undefined) return new Date(a, b, c);return new Date(a);
}
- 参数校验:调用
createDate(2023, 5, 20)
会匹配第二个签名 。
3. 面向对象方法重载
class Calculator {add(a: number, b: number): number;add(a: string, b: string): string;add(a: any, b: any): any {return a + b;}
}
4.函数重载也可以用来精确描述函数参数与返回值之间的对应关系。
function createElement(tag:'a'
):HTMLAnchorElement;
function createElement(tag:'canvas'
):HTMLCanvasElement;
function createElement(tag:'table'
):HTMLTableElement;
function createElement(tag:string
):HTMLElement {// ...
}// 上述例子的函数重载,也可以用对象表示。
type CreateElement = {(tag:'a'): HTMLAnchorElement;(tag:'canvas'): HTMLCanvasElement;(tag:'table'): HTMLTableElement;(tag:string): HTMLElement;
}
上面示例中,函数重载精确描述了参数tag
的三个值,所对应的不同的函数返回值。
四、函数重载与联合类型的取舍
场景 | 推荐方案 | 原因 |
---|---|---|
参数类型影响返回值类型 | 函数重载 | 明确返回值类型关联(如 |
参数类型不影响返回值 | 联合类型 | 简化代码结构(如`function len(x: string |
需要精确的IDE提示 | 函数重载 | 根据参数类型动态提示返回值类型 |
五、高级技巧与注意事项
-
泛型重载:
function merge<T>(arr1: T[], arr2: T[]): T[]; function merge<T, U>(arr1: T[], arr2: U[]): (T | U)[]; function merge(arr1: any[], arr2: any[]): any[] {return [...arr1, ...arr2]; }
- 支持泛型参数,处理不同类型数组的合并 。
- 支持泛型参数,处理不同类型数组的合并 。
-
构造器重载模拟:
class Square {constructor(width: number, height: number);constructor(config: { width: number; height: number });constructor(config: any, height?: number) {// 实现逻辑} }
- 通过联合类型模拟构造函数重载 。
- 通过联合类型模拟构造函数重载 。
-
避免常见错误:
- 实现签名不兼容:确保实现参数覆盖所有重载可能性。
- 过度使用重载:优先用联合类型简化代码,避免维护成本增加 ,如:
上面示例中,写法二使用联合类型,要比写法一的函数重载简单很多。// 写法一 function len(s:string):number; function len(arr:any[]):number; function len(x:any):number {return x.length; } // 写法二 function len(x:any[]|string):number {return x.length; }