Javascript的Spread和Rest运算符是什么_如何使用它们简化Javascript代码?

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() 等方法。

  • 替代 argumentsfunction 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]

常见混淆点与报错场景

它们看起来一样,但位置和上下文决定语义。写错位置会直接报语法错误,或产生意外行为。

  • ... 出现在等号右边或函数调用处 → 是 Spread;出现在函数形参列表末尾或解构左侧 → 是 Rest
  • 在对象中误用 Spread 复制有 setter 的对象,会丢失 setter 行为;JSON.parse(JSON.stringify(obj)) 也不是等价替代
  • 试图对 nullundefined 使用 Spread:会抛 TypeError: Invalid attempt to spread non-iterable instance
  • Rest 参数必须是最后一个形参,否则 function f(...rest, last) 会报错
  • 箭头函数中,arguments 不可用,必须用 rest 才能获取参数数组

真正容易被忽略的是:Spread 对嵌套对象/数组只做一层浅拷贝,Rest 在解构中遇到 undefined 元素时不会跳过,而是如实收进数组 —— 这些细节在重构老代码或处理 API 返回数据时,常常引发静默 bug。