点击查看原文完整示例代码

args 在 TypeScript 中通常指函数参数,特别是剩余参数(rest parameters)。让我详细解释各种用法和场景。

1. 剩余参数(Rest Parameters)

基本语法

// 使用 ...args 收集所有参数到一个数组中
function sum(...args: number[]): number {
  return args.reduce((total, current) => total + current, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15
console.log(sum(10, 20)); // 30

带固定参数的剩余参数

function greet(greeting: string, ...names: string[]): string {
  return `${greeting}, ${names.join(', ')}!`;
}

console.log(greet("Hello", "Alice", "Bob", "Charlie"));
// "Hello, Alice, Bob, Charlie!"

2. 函数参数对象(Arguments Object)

在 TypeScript/JavaScript 中,每个函数内部都可以访问 arguments 对象:

function traditionalSum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(traditionalSum(1, 2, 3)); // 6

注意arguments 不是数组,而是类数组对象

3. 元组类型的剩余参数

TypeScript 允许对剩余参数使用元组类型:

// 固定长度和类型的剩余参数
function processData(name: string, ...[first, second]: [number, number]): void {
  console.log(`Name: ${name}, Numbers: ${first}, ${second}`);
}

processData("John", 10, 20); // 正常
// processData("John", 10); // 错误:缺少第二个数字参数

// 混合类型的剩余参数
function mixedArgs(...args: [string, number, ...boolean[]]): void {
  console.log(args);
}

mixedArgs("hello", 42, true, false, true);

4. 在箭头函数中的使用

// 箭头函数中的剩余参数
const multiplyAll = (...numbers: number[]): number => {
  return numbers.reduce((product, num) => product * num, 1);
};

console.log(multiplyAll(2, 3, 4)); // 24

// 带泛型的剩余参数
const mergeArrays = <T>(...arrays: T[][]): T[] => {
  return arrays.flat();
};

const result = mergeArrays([1, 2], [3, 4], [5, 6]); // [1, 2, 3, 4, 5, 6]

5. 实际应用场景

场景 1:高阶函数和装饰器

// 函数日志装饰器
function logExecution<T extends (...args: any[]) => any>(
  target: T
): (...args: Parameters<T>) => ReturnType<T> {
  return function (...args: Parameters<T>): ReturnType<T> {
    console.log(`函数被调用,参数:`, args);
    const result = target(...args);
    console.log(`函数返回:`, result);
    return result;
  };
}

const loggedSum = logExecution(sum);
loggedSum(1, 2, 3); // 会输出调用日志

场景 2:配置合并

interface Config {
  timeout?: number;
  retries?: number;
  baseURL?: string;
}

function createApiClient(defaultConfig: Config, ...configs: Partial<Config>[]): Config {
  return configs.reduce((merged, config) => ({ ...merged, ...config }), defaultConfig);
}

const client = createApiClient(
  { timeout: 5000, baseURL: "/api" },
  { retries: 3 },
  { timeout: 3000 }
);
// 结果: { timeout: 3000, baseURL: "/api", retries: 3 }

场景 3:事件处理器

class EventEmitter {
  private events: Map<string, Function[]> = new Map();

  on(event: string, ...handlers: ((...args: any[]) => void)[]): void {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event)!.push(...handlers);
  }

  emit(event: string, ...args: any[]): void {
    const handlers = this.events.get(event) || [];
    handlers.forEach(handler => handler(...args));
  }
}

const emitter = new EventEmitter();
emitter.on("message", (msg: string, priority: number) => {
  console.log(`消息: ${msg}, 优先级: ${priority}`);
});

emitter.emit("message", "Hello World", 1);

6. 泛型与剩余参数

// 泛型剩余参数 - 保持类型安全
function callAll<T extends any[]>(
  ...functions: ((...args: T) => void)[]
): (...args: T) => void {
  return (...args: T) => {
    functions.forEach(fn => fn(...args));
  };
}

// 使用
const logger = (msg: string, count: number) => console.log(msg, count);
const storage = (msg: string, count: number) => localStorage.setItem('last', msg);

const combined = callAll(logger, storage);
combined("test", 5); // 两个函数都会接收到 "test" 和 5

7. 类型工具与 args

Parameters 工具类型

// 获取函数的参数类型
type SumParams = Parameters<typeof sum>; // [number[]]
type GreetParams = Parameters<typeof greet>; // [string, ...string[]]

// 实现一个包装函数
function withRetry<T extends any[], R>(
  fn: (...args: T) => R,
  retries: number
): (...args: T) => R {
  return function (...args: T): R {
    let lastError: Error;

    for (let i = 0; i < retries; i++) {
      try {
        return fn(...args);
      } catch (error) {
        lastError = error as Error;
      }
    }

    throw lastError!;
  };
}

8. 常见模式和最佳实践

模式 1:参数验证

function validateAndProcess(...inputs: (string | number)[]): void {
  if (inputs.length === 0) {
    throw new Error("至少需要一个参数");
  }

  inputs.forEach((input, index) => {
    if (input === null || input === undefined) {
      throw new Error(`参数 ${index} 不能为 null 或 undefined`);
    }
  });

  // 处理逻辑...
}

模式 2:函数组合

function compose<T extends any[]>(
  ...functions: ((...args: T) => void)[]
): (...args: T) => void {
  return (...args: T) => {
    functions.forEach(fn => {
      try {
        fn(...args);
      } catch (error) {
        console.error('函数执行错误:', error);
      }
    });
  };
}

模式 3:条件参数处理

function flexibleProcessor(
  required: string,
  ...optional: (number | string)[]
): { required: string; optional: (number | string)[] } {

  const processedOptional = optional.map(arg => {
    if (typeof arg === 'string') {
      return arg.toUpperCase();
    }
    return arg * 2;
  });

  return { required, optional: processedOptional };
}

9. 注意事项

注意 1:arguments vs 剩余参数

// ❌ 传统方式 - 没有类型安全
function oldWay() {
  console.log(arguments[0], arguments[1]); // 没有类型提示
}

// ✅ 现代方式 - 类型安全
function newWay(...args: [string, number]) {
  console.log(args[0], args[1]); // 有完整的类型提示
}

注意 2:性能考虑

// 对于性能敏感的场景,避免在热路径中使用剩余参数
function optimizedSum(a: number, b: number, c?: number, d?: number): number {
  let total = a + b;
  if (c !== undefined) total += c;
  if (d !== undefined) total += d;
  return total;
}

// 比使用 ...args: number[] 更高效

💎 总结

在 TypeScript 中,args 主要涉及:

  1. 剩余参数 (...args):收集多个参数到数组
  2. arguments 对象:函数内可访问的类数组对象
  3. 元组类型的剩余参数:提供更精确的类型控制
  4. 泛型剩余参数:保持类型安全的可变参数

最佳实践

  • 优先使用剩余参数而非 arguments 对象
  • 为剩余参数提供明确的类型注解
  • 在需要精确类型控制时使用元组类型
  • 考虑性能影响,在热路径中避免不必要的数组创建

args 是 TypeScript 函数编程中的重要概念,合理使用可以大大增强代码的灵活性和类型安全性。

希望这些信息能帮助您。如果还有其他想了解的,随时可以留言。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐