先记住这个答案
剩余参数写成 function f(a, ...rest),它把前面具名参数用剩下的实参收集成一个真正的数组。arguments 是普通函数自带的一个类数组对象,有 length 和下标,但没有 map、filter 这些数组方法,要先用 Array.from(arguments) 转换。除了类型,还有三点区别:剩余参数只收集「剩下的」,arguments 永远包含全部实参;箭头函数没有自己的 arguments,但可以用剩余参数;非严格模式下 arguments 会和形参互相影响,剩余参数是一个独立的新数组,不会。
- 剩余参数是真数组,
arguments是类数组 - 剩余参数只收集具名参数之后的实参
- 箭头函数没有
arguments,只能用剩余参数 - 剩余参数只能有一个,必须放在最后
同一个需求,两种写法
写一个求和函数,第一个参数是标签,后面是任意多个数字。
arguments 和箭头函数JavaScriptfunction sum(label, ...nums) {
console.log(arguments.length, nums.length) // arguments 把 label 也算上了
console.log(Array.isArray(arguments), Array.isArray(nums))
return `${label}:${nums.reduce((total, n) => total + n, 0)}`
}
console.log(sum('total', 1, 2, 3))
function legacySum() {
// arguments 不是数组,要先转成数组才能 reduce
return Array.from(arguments).reduce((total, n) => total + n, 0)
}
console.log(legacySum(1, 2, 3))
const multiply = (...nums) => nums.reduce((result, n) => result * n, 1)
console.log(multiply(2, 3, 4))查看输出与解释
4 3
false true
total:6
6
24arguments.length 是 4,因为它把 label 也算进去了;nums 只有后面三个数字。Array.isArray 的结果说明只有 nums 是真数组,可以直接调用 reduce。
legacySum 是 ES6 之前的写法,函数签名里看不出它接收参数,还要多一步转换。multiply 是箭头函数,里面没法用 arguments,剩余参数是唯一选择。
arguments 和形参联动的坑
非严格模式下,如果函数的参数都是简单写法(没有默认值、剩余参数和解构),arguments 和形参是联动的。比如 function f(a) { arguments[0] = 9; return a },调用 f(1) 返回的是 9,改 arguments[0] 就改了 a,反过来也一样。这种隐式联动很容易造成难查的 bug。
只要参数列表里用了剩余参数、默认值或解构中的任何一种,这种联动就会关闭,严格模式下也没有联动。剩余参数本身就是一个新创建的数组,改它的元素不会影响任何形参,数据怎么流动一眼就能看清。
- 剩余参数的语法限制
- 一个函数只能有一个剩余参数,而且必须是最后一个形参,
function f(...a, b)是语法错误,因为引擎没法判断哪些实参归a。剩余参数不能有默认值,后面也不能跟逗号。没有多余实参时,它是空数组[],不是undefined,可以放心直接遍历。另外,剩余参数不计入函数的length。
容易答错的地方
- 以为剩余参数包含全部实参
- 它只收集具名形参之后的部分。需要拿到全部参数,就只写一个剩余参数
(...args),不要在前面加具名参数。 - 以为箭头函数里的
arguments是自己的 - 箭头函数没有自己的
arguments。写在普通函数里的箭头函数读arguments,拿到的是外层函数的;写在浏览器脚本或 ES 模块的顶层,直接报ReferenceError。箭头函数要拿参数,只能用剩余参数。
面试官还会怎么问?
剩余参数和展开语法有什么区别?
写法一样都是三个点,方向相反。写在形参位置 function f(...args) 是把多个实参收集成一个数组;写在调用或字面量里 f(...arr)、[...arr] 是把一个数组拆开成多个值。可以记成「定义时收,调用时拆」。
剩余参数能配合解构用吗?
能。function f(...[first, second]) 会先收集成数组再解构。更常见的是在解构赋值里用剩余元素,比如 const [head, ...tail] = list、const { id, ...others } = user,后者常用来从对象里去掉某个字段。
TypeScript 里怎么给剩余参数标类型?
写成 (...nums: number[]) 表示任意多个数字。如果每个位置类型不同,可以用元组,比如 (...args: [string, number]),调用时参数个数和类型都会被检查。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。