javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > TypeScript 函数重载

TypeScript 函数重载(Overloads)的实现示例

作者:烛衔溟

本文主要介绍了TypeScript 函数重载(Overloads)的实现示例,它详细描述了重载签名、实现签名的定义和使用规则,以及参数数量和类型不同的处理方式,感兴趣的可以了解一下

一、为什么需要重载

1.1 问题场景

在 JavaScript 中,同一个函数可能根据传入参数的类型或数量执行不同逻辑并返回不同类型。TypeScript 的类型系统需要准确描述这种关系。

例如,一个函数接收字符串或字符串数组,分别返回翻转后的字符串或翻转后的数组:

function reverse(input: string | string[]): string | string[] {
    if (typeof input === "string") {
        return input.split("").reverse().join("");
    } else {
        return [...input].reverse();
    }
}

const revStr = reverse("hello");   // 类型推断为 string | string[]
const revArr = reverse(["a","b"]); // 类型推断为 string | string[]

问题:调用者知道传入的是字符串,返回的也一定是字符串,但 TypeScript 推断的是联合类型,丢失了精确信息。

1.2 重载的解决方案

通过声明多个重载签名(overload signatures),告诉 TypeScript 不同参数组合对应的返回值类型,再提供一个实现签名(implementation signature)来实际执行逻辑。

// 重载签名
function reverse(input: string): string;
function reverse(input: string[]): string[];

// 实现签名
function reverse(input: string | string[]): string | string[] {
    if (typeof input === "string") {
        return input.split("").reverse().join("");
    } else {
        return [...input].reverse();
    }
}

const revStr = reverse("hello");   // 类型为 string
const revArr = reverse(["a","b"]); // 类型为 string[]

二、重载语法与规则

2.1 重载签名(Overload Signatures)

function makeDate(timestamp: number): Date;           // 重载1
function makeDate(year: number, month: number, day: number): Date; // 重载2
function makeDate(...args: number[]): Date {           // 实现签名
    if (args.length === 1) {
        return new Date(args[0]);
    } else if (args.length === 3) {
        return new Date(args[0], args[1], args[2]);
    }
    throw new Error("Invalid arguments");
}

2.2 实现签名(Implementation Signature)

2.3 重载解析规则

TypeScript 编译器按顺序匹配重载签名,选择第一个匹配的。因此,应将更具体的重载放在前面,更宽泛的放在后面。

function fn(x: string): string;
function fn(x: number): number;
function fn(x: any): any { return x; }

fn("a");  // 匹配第一个,返回 string
fn(1);    // 匹配第二个,返回 number

如果将 number 重载放在 string 前面,对字符串调用仍会匹配第二个(因为 string 可赋值给 any,但 string 不能赋值给 number,所以不匹配),顺序会影响能否找到正确重载。

2.4 参数数量不同的重载

重载可以基于参数个数区分,但需要实现签名中处理可选参数或剩余参数。

function format(): string;
function format(prefix: string): string;
function format(prefix?: string): string {
    return prefix ? `${prefix}: data` : "data";
}

2.5 参数类型不同的重载

function getLength(value: string): number;
function getLength(value: any[]): number;
function getLength(value: any): number {
    return value.length;
}

getLength("hello"); // 5
getLength([1,2,3]); // 3

注意:如果两个重载的参数类型有重叠(例如 stringany),需要确保顺序或使用更精确的类型。

三、重载 vs 联合类型

很多场景下可以用联合类型参数配合类型守卫,而不必使用重载。但重载在返回值类型不同且与参数类型有精确映射关系时更有优势。

场景推荐方式原因
参数类型不同,返回值类型也不同(且一一对应)重载返回值类型精确
参数类型不同,返回值类型相同联合类型参数代码更简洁
参数是联合类型,返回值也是联合类型但无映射关系联合类型参数 + 类型守卫避免重载冗余

示例:返回值类型相同,不需要重载。

// 不需要重载
function log(value: string | number): void {
    console.log(value);
}

示例:返回值类型与参数类型一一对应,重载更好。

// 重载
function pick(value: string, start: number, length?: number): string;
function pick<T>(value: T[], start: number, length?: number): T[];
function pick(value: any, start: number, length?: number): any {
    // 实现
}

四、箭头函数不支持重载

箭头函数(函数表达式)不能像函数声明那样写多个重载签名。替代方案:

// 错误:箭头函数不能有重载
// const reverse = (input: string): string;
// const reverse = (input: string[]): string[]; // ❌

// 正确:使用函数声明
function reverse(input: string): string;
function reverse(input: string[]): string[];
function reverse(input: string | string[]): string | string[] {
    // 实现
}

如果需要保留箭头函数的 this 绑定,可以内部调用一个具名函数。

五、常见错误与注意事项

5.1 实现签名可调用但不应被外部调用

实现签名的类型不会暴露给外部,但如果你直接调用实现签名(例如传入了联合类型的参数),TypeScript 会报错提示“此调用不匹配任何重载”。

function fn(x: string): string;
function fn(x: number): number;
function fn(x: any): any { return x; }

fn("a");   // OK
fn(1);     // OK
fn(true);  // ❌ 不匹配任何重载

5.2 重载签名顺序不当导致匹配错误

function fn(x: any): any;
function fn(x: string): string;
function fn(x: any): any { return x; }

fn("a"); // 匹配第一个(any),返回 any,丢失精确类型

应将更具体的重载放在前面。

5.3 实现签名不够兼容

function fn(x: string): string;
function fn(x: number): number;
// 实现签名参数类型不兼容(缺少 number)
function fn(x: string): string { // ❌ 实现签名与重载不兼容
    return x;
}

实现签名必须能处理所有重载签名的参数。

5.4 不必要的重载

如果一个函数可以用可选参数或默认参数实现,就不需要重载。

// 不必要的重载
function greet(): string;
function greet(name: string): string;
function greet(name?: string): string {
    return name ? `Hello, ${name}` : "Hello";
}

// 更好的写法(默认参数)
function greet(name: string = "Guest"): string {
    return `Hello, ${name}`;
}

5.5 重载与泛型

如果重载逻辑可以用泛型解决,优先考虑泛型(后续文章会讲)。泛型通常更简洁。

// 重载方式
function first(arr: string[]): string;
function first<T>(arr: T[]): T;
function first(arr: any[]): any {
    return arr[0];
}

// 泛型方式(一个签名就够了)
function first<T>(arr: T[]): T {
    return arr[0];
}

六、综合示例

// 定义一个坐标转换函数:支持不同输入格式,返回统一的对象或数组
type Point2D = { x: number; y: number };

// 重载签名
function toPoint(coords: [number, number]): Point2D;
function toPoint(x: number, y: number): Point2D;
function toPoint(coords: string): Point2D;
function toPoint(x: number | string | [number, number], y?: number): Point2D {
    if (typeof x === "number" && typeof y === "number") {
        return { x, y };
    }
    if (Array.isArray(x) && x.length === 2) {
        return { x: x[0], y: x[1] };
    }
    if (typeof x === "string") {
        const parts = x.split(",").map(Number);
        if (parts.length === 2 && !parts.some(isNaN)) {
            return { x: parts[0], y: parts[1] };
        }
    }
    throw new Error("Invalid input");
}

// 使用
const p1 = toPoint(10, 20);           // Point2D
const p2 = toPoint([30, 40]);          // Point2D
const p3 = toPoint("50,60");           // Point2D

// 另一个示例:创建日期,支持时间戳或年月日
function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(arg1: number, arg2?: number, arg3?: number): Date {
    if (arg2 !== undefined && arg3 !== undefined) {
        return new Date(arg1, arg2, arg3);
    }
    return new Date(arg1);
}

const d1 = createDate(1672531200000);   // Date
const d2 = createDate(2025, 3, 20);     // Date

七、小结

概念说明
重载签名多个,无函数体,定义外部可调用的参数与返回值组合
实现签名一个,有函数体,必须兼容所有重载签名
匹配顺序按重载签名顺序匹配,具体重载在前,宽泛在后
参数数量不同实现签名中用可选参数或剩余参数处理
参数类型不同实现签名中用联合类型处理
箭头函数不支持重载,使用函数声明替代
与泛型关系部分重载可被泛型简化

到此这篇关于TypeScript 函数重载(Overloads)的实现示例的文章就介绍到这了,更多相关TypeScript 函数重载内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文