Spread运算符用于展开可迭代对象,Rest运算符用于收集剩余参数;二者语义相反,位置决定功能,误用将导致语法错误或静默bug。
Spread 和 Rest 运算符都用 ... 表示,但作用相反:Spread 是“拆开”,Rest 是“收拢”。它们不是语法糖的替代品,而是改变了你处理数组、对象和函数参数的底层思维模式。
它把数组、字符串、Map、Set 等“打散”成独立元素,常用于构造新数组/对象或传参。
[...arr1, ...arr2] 比 arr1.concat(arr2) 更直观,且不修改原数组[...arr]
比 arr.slice() 或 Array.from(arr) 更简洁Math.max(...numbers) 可直接展开,而 Math.max(numbers) 会返回 NaN
{ ...obj1, ...obj2 } 实现浅合并,后出现的同名属性覆盖前者... 仅复制自身可枚举属性,不处理原型链、symbol 键或 getterconst a = [1, 2]; const b = [3, 4]; console.log([...a, ...b]); // [1, 2, 3, 4]const user = { name: 'Alice', age: 30 }; const updated = { ...user, city: 'Beijing' }; // { name: 'Alice', age: 30, city: 'Beijing' }
它必须是函数最后一个形参,把多余的实参“收拢”为一个数组。和 arguments 不同,rest 是真数组,可直接用 .map()、.filter() 等方法。
arguments:function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }
const [first, second, ...rest] = [1, 2, 3, 4, 5]; → rest 是 [3, 4, 5]
const { id, ...data } = { id: 1, name: 'Bob', role: 'admin' };
function f(a, ..., c) 是语法错误function multiply(base, ...factors) {
return factors.map(n => base * n);
}
multiply(2, 3, 4, 5); // [6, 8, 10]
const scores = [95, 82, 76, 91, 88];
const [top, second, ...others] = scores;
console.log(others); // [76, 91, 88]
它们看起来一样,但位置和上下文决定语义。写错位置会直接报语法错误,或产生意外行为。
... 出现在等号右边或函数调用处 → 是 Spread;出现在函数形参列表末尾或解构左侧 → 是 RestJSON.parse(JSON.stringify(obj)) 也不是等价替代null 或 undefined 使用 Spread:会抛 TypeError: Invalid attempt to spread non-iterable instance
function f(...rest, last) 会报错arguments 不可用,必须用 rest 才能获取参数数组真正容易被忽略的是:Spread 对嵌套对象/数组只做一层浅拷贝,Rest 在解构中遇到 undefined 元素时不会跳过,而是如实收进数组 —— 这些细节在重构老代码或处理 API 返回数据时,常常引发静默 bug。