剩余参数

剩余参数允许函数接受不定数量的参数,将它们收集到一个数组中。使用 ... 语法,剩余参数必须是函数的最后一个参数。

基本语法

在参数名前添加 ...,该参数会收集所有剩余的参数值。

function sum(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0)
}

console.log(sum(1, 2, 3))
console.log(sum(10, 20, 30, 40, 50))
console.log(sum())

numbers 是剩余参数,类型是 number[]。调用时可以传入任意数量的数字参数。

剩余参数与其他参数

剩余参数必须放在所有固定参数之后。

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

console.log(greet("你好", "张三"))
console.log(greet("早上好", "张三", "李四", "王五"))

第一个参数 greeting 是固定参数,后面的所有参数都被收集到 names 数组中。

剩余参数的类型

剩余参数的类型必须是数组类型或元组类型。

function logAll(...messages: string[]): void {
  messages.forEach(msg => console.log(msg))
}

logAll("消息1", "消息2", "消息3")

function printCoordinates(...coords: [number, number, number]): void {
  const [x, y, z] = coords
  console.log(`坐标: (${x}, ${y}, ${z})`)
}

printCoordinates(10, 20, 30)

数组类型接受任意数量的参数,元组类型限制参数的数量和类型。

剩余参数与泛型

剩余参数可以与泛型结合使用,实现类型安全的可变参数函数。

function combine<T>(...arrays: T[][]): T[] {
  return arrays.flat()
}

console.log(combine([1, 2], [3, 4], [5, 6]))
console.log(combine(["a", "b"], ["c", "d"]))

function pickFirst<T>(...items: T[]): T | undefined {
  return items[0]
}

console.log(pickFirst(1, 2, 3))
console.log(pickFirst("a", "b", "c"))
console.log(pickFirst())

泛型让剩余参数的类型更加灵活,同时保持类型安全。

剩余参数与箭头函数

箭头函数也可以使用剩余参数。

const multiply = (...factors: number[]): number => {
  return factors.reduce((product, n) => product * n, 1)
}

console.log(multiply(2, 3, 4))

const format = (template: string, ...values: string[]): string => {
  return template.replace(/{}/g, () => values.shift() || "")
}

console.log(format("你好,{},欢迎来到{}", "张三", "北京"))

箭头函数的剩余参数语法与普通函数相同。

剩余参数与解构

剩余参数可以与解构语法结合使用。

function processInput(input: string, ...[first, second, ...rest]: string[]): void {
  console.log(`输入: ${input}`)
  console.log(`第一: ${first}`)
  console.log(`第二: ${second}`)
  console.log(`其余: ${rest}`)
}

processInput("测试", "a", "b", "c", "d")

解构语法在参数位置直接展开剩余参数,但这种方式可读性较差,通常不推荐使用。

arguments 与剩余参数

在普通函数中,arguments 对象包含所有参数。但剩余参数是更好的选择。

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

function sumNew(...numbers: number[]): number {
  return numbers.reduce((total, n) => total + n, 0)
}

console.log(sumOld(1, 2, 3))
console.log(sumNew(1, 2, 3))

剩余参数的优势:

  • 有明确的类型
  • 是真正的数组,可以使用数组方法
  • 箭头函数中也可以使用

实际应用

剩余参数在实际开发中有很多应用场景。

function formatMessage(template: string, ...args: any[]): string {
  return template.replace(/{(\d+)}/g, (match, index) => {
    return typeof args[index] !== "undefined" ? args[index] : match
  })
}

console.log(formatMessage("你好,{0},你有{1}条新消息", "张三", 5))

function createLogger(prefix: string) {
  return (...messages: string[]): void => {
    const timestamp = new Date().toISOString()
    messages.forEach(msg => {
      console.log(`[${timestamp}] [${prefix}] ${msg}`)
    })
  }
}

const appLogger = createLogger("APP")
appLogger("应用启动", "加载配置", "初始化完成")

function mergeObjects<T extends object>(...objects: T[]): T {
  return objects.reduce((result, obj) => ({ ...result, ...obj }), {} as T)
}

const merged = mergeObjects(
  { a: 1 },
  { b: 2 },
  { c: 3 }
)
console.log(merged)

剩余参数让函数可以处理不定数量的参数,非常适合日志、格式化、合并等操作。

小结

剩余参数使用 ... 语法收集多个参数到数组中。它必须是函数的最后一个参数,类型必须是数组或元组。相比 arguments 对象,剩余参数有明确的类型,是真正的数组,在箭头函数中也能使用。剩余参数是实现可变参数函数的标准方式。