【前端面试小册】JS-第7节:JavaScript 函数重载

一、JavaScript 不支持函数重载

什么是函数重载?

函数重载(Function Overloading)是指在同一个作用域内,定义多个同名函数,但它们的参数类型参数个数不同。调用时,编译器根据参数自动选择匹配的函数。

典型例子(Java/C++)

// Java 中的函数重载
public class Calculator {
    // 两个整数相加
    public int add(int a, int b) {
        return a + b;
    }
    
    // 三个整数相加
    public int add(int a, int b, int c) {
        return a + b + c;
    }
    
    // 浮点数相加
    public double add(double a, double b) {
        return a + b;
    }
}

// 调用时根据参数自动选择
Calculator calc = new Calculator();
calc.add(1, 2);        // 调用第一个 add 方法
calc.add(1, 2, 3);    // 调用第二个 add 方法
calc.add(1.5, 2.5);   // 调用第三个 add 方法

JavaScript 中的情况

JavaScript 不支持函数重载,后定义的函数会覆盖前面的同名函数。

// ❌ JavaScript 不支持函数重载
function overload(a) {
  console.log('一个参数');
}

function overload(a, b) {
  console.log('两个参数');
}

// 在支持重载的语言中(如 Java)
// overload(1);      // 输出:一个参数
// overload(1, 2);  // 输出:两个参数

// 在 JavaScript 中
overload(1);      // 输出:两个参数(覆盖了前面的函数)
overload(1, 2);  // 输出:两个参数

原因分析

graph TD
    A[定义 function overload a] --> B[函数被注册到作用域]
    B --> C[定义 function overload a,b]
    C --> D[同名函数覆盖前面的定义]
    D --> E[只保留最后一个定义]
    E --> F[无论传几个参数都调用最后一个函数]

关键理解

  • JavaScript 函数是一等公民,可以作为值传递
  • 后定义的函数会覆盖前面同名的函数
  • JavaScript 是动态类型语言,函数签名不包含参数类型信息

二、实现函数重载的解决方案

虽然 JavaScript 不支持原生函数重载,但可以通过多种方式模拟实现

方案一:使用 arguments 对象(传统方式)

function overload() {
  // 根据参数个数判断
  if (arguments.length === 1) {
    console.log('一个参数:', arguments[0]);
  } else if (arguments.length === 2) {
    console.log('两个参数:', arguments[0], arguments[1]);
  } else if (arguments.length === 3) {
    console.log('三个参数:', arguments[0], arguments[1], arguments[2]);
  } else {
    console.log('参数过多或过少');
  }
}

overload(1);           // 输出:一个参数: 1
overload(1, 2);       // 输出:两个参数: 1 2
overload(1, 2, 3);    // 输出:三个参数: 1 2 3

优点

  • ✅ 兼容性好,ES5 及之前版本都支持
  • ✅ 实现简单直接

缺点

  • ❌ 代码可读性较差
  • ❌ 参数语义不清晰
  • arguments 是类数组,不是真正的数组

方案二:使用 ES6 剩余参数(推荐)

function overload(...args) {
  if (args.length === 1) {
    console.log('一个参数:', args[0]);
  } else if (args.length === 2) {
    console.log('两个参数:', args[0], args[1]);
  } else if (args.length === 3) {
    console.log('三个参数:', args[0], args[1], args[2]);
  } else {
    console.log('参数过多或过少');
  }
}

overload(1);           // 输出:一个参数: 1
overload(1, 2);       // 输出:两个参数: 1 2
overload(1, 2, 3);    // 输出:三个参数: 1 2 3

优点

  • args 是真正的数组,可以使用数组方法
  • ✅ 语法更现代
  • ✅ 可读性更好

方案三:根据参数类型判断

function overload(...args) {
  // 根据参数类型和数量判断
  if (args.length === 1) {
    if (typeof args[0] === 'string') {
      console.log('处理字符串:', args[0]);
    } else if (typeof args[0] === 'number') {
      console.log('处理数字:', args[0]);
    }
  } else if (args.length === 2) {
    if (typeof args[0] === 'string' && typeof args[1] === 'number') {
      console.log('字符串 + 数字:', args[0], args[1]);
    } else if (typeof args[0] === 'number' && typeof args[1] === 'number') {
      console.log('数字 + 数字:', args[0] + args[1]);
    }
  }
}

overload('hello');        // 输出:处理字符串: hello
overload(42);            // 输出:处理数字: 42
overload('age', 18);     // 输出:字符串 + 数字: age 18
overload(10, 20);        // 输出:数字 + 数字: 30

三、实际应用场景

场景一:API 请求函数

// 根据参数不同,执行不同的请求逻辑
async function fetchData(...args) {
  if (args.length === 1 && typeof args[0] === 'string') {
    // 单个 ID 查询
    const id = args[0];
    const response = await fetch(`/api/users/${id}`);
    return response.json();
  } else if (args.length === 1 && typeof args[0] === 'object') {
    // 查询条件对象
    const params = new URLSearchParams(args[0]);
    const response = await fetch(`/api/users?${params}`);
    return response.json();
  } else if (args.length === 2) {
    // 分页查询
    const [page, pageSize] = args;
    const response = await fetch(`/api/users?page=${page}&pageSize=${pageSize}`);
    return response.json();
  } else {
    throw new Error('无效的参数');
  }
}

// 使用示例
await fetchData('123');                    // 根据 ID 查询
await fetchData({ name: 'John' });         // 根据条件查询
await fetchData(1, 10);                    // 分页查询

场景二:工具函数

// 格式化日期函数,支持多种输入格式
function formatDate(...args) {
  if (args.length === 0) {
    // 无参数:使用当前时间
    return new Date().toLocaleDateString();
  } else if (args.length === 1) {
    if (typeof args[0] === 'string') {
      // 字符串格式的日期
      return new Date(args[0]).toLocaleDateString();
    } else if (args[0] instanceof Date) {
      // Date 对象
      return args[0].toLocaleDateString();
    } else if (typeof args[0] === 'number') {
      // 时间戳
      return new Date(args[0]).toLocaleDateString();
    }
  } else if (args.length === 3) {
    // 年、月、日分开传入
    const [year, month, day] = args;
    return new Date(year, month - 1, day).toLocaleDateString();
  }
  throw new Error('无效的参数格式');
}

// 使用示例
formatDate();                              // 当前日期
formatDate('2024-01-01');                  // 字符串日期
formatDate(new Date());                    // Date 对象
formatDate(1704067200000);                 // 时间戳
formatDate(2024, 1, 1);                    // 年、月、日

场景三:更优雅的实现方式

// 使用策略模式实现更清晰的重载
function createOverload() {
  const callbacks = [];
  
  function overload(...args) {
    // 根据参数个数和类型匹配最合适的函数
    const match = callbacks.find(cb => {
      if (cb.length !== args.length) return false;
      return cb.types.every((type, index) => {
        const arg = args[index];
        if (type === 'number') return typeof arg === 'number';
        if (type === 'string') return typeof arg === 'string';
        if (type === 'object') return typeof arg === 'object' && arg !== null;
        return true;
      });
    });
    
    if (match) {
      return match.fn(...args);
    }
    throw new Error('没有匹配的函数重载');
  }
  
  // 注册重载函数
  overload.add = function(types, fn) {
    callbacks.push({ types, fn });
    return overload;
  };
  
  return overload;
}

// 使用示例
const format = createOverload();

format.add(['string'], (str) => {
  return `字符串: ${str}`;
});

format.add(['number'], (num) => {
  return `数字: ${num}`;
});

format.add(['string', 'number'], (str, num) => {
  return `字符串+数字: ${str} ${num}`;
});

console.log(format('hello'));           // 输出:字符串: hello
console.log(format(42));                // 输出:数字: 42
console.log(format('age', 18));         // 输出:字符串+数字: age 18

四、TypeScript 中的函数重载

TypeScript 提供了类型级别的函数重载,可以在编译时进行类型检查:

// TypeScript 函数重载
function formatDate(): string;
function formatDate(date: Date): string;
function formatDate(timestamp: number): string;
function formatDate(year: number, month: number, day: number): string;
function formatDate(...args: any[]): string {
  if (args.length === 0) {
    return new Date().toLocaleDateString();
  } else if (args.length === 1) {
    if (args[0] instanceof Date) {
      return args[0].toLocaleDateString();
    } else if (typeof args[0] === 'number') {
      return new Date(args[0]).toLocaleDateString();
    }
  } else if (args.length === 3) {
    const [year, month, day] = args;
    return new Date(year, month - 1, day).toLocaleDateString();
  }
  throw new Error('无效的参数');
}

// 使用时有类型提示和检查
formatDate();                    // ✅ 正确
formatDate(new Date());          // ✅ 正确
formatDate(1704067200000);       // ✅ 正确
formatDate(2024, 1, 1);         // ✅ 正确
formatDate('2024-01-01');        // ❌ TypeScript 编译错误

优势

  • ✅ 编译时类型检查
  • ✅ IDE 智能提示
  • ✅ 代码可读性更好
  • ✅ 运行时性能无影响(编译后仍然是 JavaScript)

五、函数重载 vs 默认参数

有时候,使用默认参数可能比重载更简洁:

// ❌ 使用重载方式
function greet(...args) {
  if (args.length === 1) {
    console.log(`Hello, ${args[0]}!`);
  } else if (args.length === 2) {
    console.log(`Hello, ${args[0]}! You are ${args[1]} years old.`);
  }
}

// ✅ 使用默认参数(更简洁)
function greet(name, age = null) {
  if (age === null) {
    console.log(`Hello, ${name}!`);
  } else {
    console.log(`Hello, ${name}! You are ${age} years old.`);
  }
}

greet('John');           // 输出:Hello, John!
greet('John', 25);       // 输出:Hello, John! You are 25 years old.

建议

  • 参数数量不同 → 考虑使用默认参数
  • 参数类型不同或逻辑差异大 → 使用函数重载

六、面试要点总结

核心知识点

  1. JavaScript 不支持函数重载:后定义的函数会覆盖前面的同名函数
  2. 实现方式
    • 使用 arguments 对象(传统方式)
    • 使用 ES6 剩余参数(推荐)
    • 根据参数类型判断
  3. TypeScript 支持:类型级别的函数重载,编译时检查

常见面试题

Q1: JavaScript 支持函数重载吗?

答:不支持。JavaScript 是动态类型语言,后定义的函数会覆盖前面的同名函数。但可以通过检查参数个数和类型来模拟实现。

Q2: 如何实现 JavaScript 函数重载?

答:

  1. 使用 arguments 对象或剩余参数获取所有参数
  2. 根据参数个数或类型判断
  3. 执行对应的逻辑
  4. 也可以使用策略模式实现更优雅的重载机制

Q3: TypeScript 中的函数重载和 JavaScript 中的模拟有什么不同?

答:

  • TypeScript:类型级别的重载,编译时检查,提供类型安全
  • JavaScript:运行时判断,通过代码逻辑实现,无类型检查

实战建议

  • ✅ 简单场景使用默认参数即可
  • ✅ 复杂场景使用剩余参数 + 类型判断实现重载
  • ✅ 大型项目使用 TypeScript 的类型重载
  • ✅ 保持代码可读性,避免过度设计
#前端面试#
前端面试小册 文章被收录于专栏

每天更新3-4节,持续更新中... 目标:50天学完,上岸银行总行!

全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务