Spread运算符用于展开可迭代对象,Rest运算符用于收集剩余参数;二者语义相反,位置决定功能,误用将导致语法错误或静默bug。

Spread 和 Rest 运算符都用 ... 表示,但作用相反:Spread 是“拆开”,Rest 是“收拢”。它们不是语法糖的替代品,而是改变了你处理数组、对象和函数参数的底层思维模式。
Spread 运算符用于展开可迭代对象
它把数组、字符串、Map、Set 等“打散”成独立元素,常用于构造新数组/对象或传参。
- 数组合并:
[...arr1, ...arr2]比arr1.concat(arr2)更直观,且不修改原数组 - 浅拷贝数组:
[...arr]比arr.slice()或Array.from(arr)更简洁 - 函数调用传参:
Math.max(...numbers)可直接展开,而Math.max(numbers)会返回NaN - 对象展开(ES2018+):
{ ...obj1, ...obj2 }实现浅合并,后出现的同名属性覆盖前者 - 注意:对普通对象使用
...仅复制自身可枚举属性,不处理原型链、symbol 键或 getter
const 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' }
Rest 运算符用于收集剩余参数
它必须是函数最后一个形参,把多余的实参“收拢”为一个数组。和 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] - 对象解构中不能直接用 rest 收集剩余属性(ES2018+ 支持,但需确保所有键已知):
const { id, ...data } = { id: 1, name: 'Bob', role: 'admin' }; - Rest 不支持“跳过中间参数”,比如
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]
常见混淆点与报错场景
它们看起来一样,但位置和上下文决定语义。写错位置会直接报语法错误,或产生意外行为。
立即学习“Java免费学习笔记(深入)”;
-
...出现在等号右边或函数调用处 → 是 Spread;出现在函数形参列表末尾或解构左侧 → 是 Rest - 在对象中误用 Spread 复制有 setter 的对象,会丢失 setter 行为;
JSON.parse(JSON.stringify(obj))也不是等价替代 - 试图对
null或undefined使用 Spread:会抛TypeError: Invalid attempt to spread non-iterable instance - Rest 参数必须是最后一个形参,否则
function f(...rest, last)会报错 - 箭头函数中,
arguments不可用,必须用 rest 才能获取参数数组
真正容易被忽略的是:Spread 对嵌套对象/数组只做一层浅拷贝,Rest 在解构中遇到 undefined 元素时不会跳过,而是如实收进数组 —— 这些细节在重构老代码或处理 API 返回数据时,常常引发静默 bug。










