人人都会AI编程

11.2 扩展运算符与剩余运算符

更新时间:2026-07-11

ES6 引入的 ... 运算符可能是 JavaScript 中最“一符多用”的语法。它根据出现的位置不同,分别扮演“扩展”(spread)和“剩余”(rest)两种角色。理解这两种用途,能极大简化数组、对象和函数参数的常见操作。

11.2.1 扩展运算符(Spread)

扩展运算符将一个可迭代对象(如数组、字符串、Set、Map)或对象展开为逗号分隔的序列。它只能用在函数调用、数组字面量或对象字面量中。

数组展开

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组(等价于 const merged = arr1.concat(arr2))
const merged = [...arr1, ...arr2]; // [1,2,3,4,5,6]

// 浅拷贝数组
const copy = [...arr1]; // [1,2,3],与 arr1 不同引用

// 将数组元素作为独立参数传给函数
console.log(...arr1); // 相当于 console.log(1, 2, 3)
Math.max(...arr1);    // 相当于 Math.max(1, 2, 3)

对象展开(ES2018)

对象展开将源对象自身的可枚举属性浅拷贝到目标对象中。

const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };

// 合并对象(后展开的属性会覆盖同名属性)
const merged = { ...obj1, ...obj2 }; // { a:1, b:2, c:3, d:4 }

// 浅拷贝对象
const copy = { ...obj1 }; // { a:1, b:2 }

// 添加或覆盖属性
const updated = { ...obj1, b: 99, e: 5 }; // { a:1, b:99, e:5 }

字符串展开

字符串也是可迭代对象,... 可以将其拆分为字符数组。

const chars = [..."hello"]; // ['h','e','l','l','o']

实际场景

  • 不可变数据更新:在 React/Vue 中更新状态时,常用展开避免直接修改原对象。
  • 函数参数展开:将数组元素作为参数传入函数。
  • 类数组转换[...arguments][...nodeList] 将类数组转为真正的数组。

11.2.2 剩余运算符(Rest)

剩余运算符将多个独立元素收集到一个数组中。它只出现在函数参数定义解构赋值的左侧。

函数剩余参数

将不确定数量的参数收集为数组,彻底取代 arguments 对象。

function sum(...numbers) {
  return numbers.reduce((acc, cur) => acc + cur, 0);
}

sum(1, 2, 3);   // 6
sum(10, 20);    // 30
sum();          // 0(空数组)

优势:

  • 剩余参数是真正的数组,可以直接使用数组方法,无需 Array.prototype.slice.call(arguments)
  • 箭头函数中没有 arguments 对象,剩余参数是唯一选择。
  • 可以和其他命名参数结合,但必须放在最后
function greet(greeting, ...names) {
  return `${greeting}, ${names.join(' and ')}`;
}
greet('Hello', 'Alice', 'Bob'); // "Hello, Alice and Bob"

解构剩余参数

在数组或对象解构时,将未匹配的剩余元素收集到新变量中。

const [first, second, ...others] = [1, 2, 3, 4, 5];
console.log(first);  // 1
console.log(second); // 2
console.log(others); // [3,4,5]

const { a, b, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a);    // 1
console.log(rest); // { c:3, d:4 }

对象解构中的 ...rest 会收集所有未被提取的自身可枚举属性,形成一个新对象。常用于分离出某些已知属性。

11.2.3 区分扩展与剩余的秘诀

关键看 ... 出现在哪里、做什么

  • 扩展(Spread):放在“值的提供方”——函数调用中的实参列表、数组/对象字面量内部。将整体拆成个体。
  • 剩余(Rest):放在“值的收集方”——函数形参定义、解构赋值左侧。将多个个体合成一个数组/对象。
// 扩展:在函数调用时展开数组
const nums = [1, 2, 3];
Math.max(...nums);

// 剩余:在函数定义时收集参数
function logAll(...args) { console.log(args); }

11.2.4 注意事项

  • 浅拷贝本质:无论数组还是对象的展开,都只做一层浅拷贝。如果源对象中包含引用类型(嵌套对象、数组),拷贝后的对象仍共享同一引用。
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
copy.b.c = 99;
console.log(original.b.c); // 99,两个对象的 b 指向同一个内部对象

处理深层嵌套数据时,应使用深拷贝库(如 lodash.cloneDeep)或手动递归拷贝。

  • 只能展开可迭代对象:数组、字符串、Map、Set、arguments 等拥有 Symbol.iterator 的对象才可以在数组字面量或函数调用中使用 ... 展开。对象展开依赖的是 {...obj} 语法,它基于对象自身的可枚举属性,而非迭代协议。
  • 剩余参数必须位于最后function fn(a, ...rest, b) {} 会直接报错,rest 必须是最后一个形参。
  • 解构中的 ... 操作优先级:剩余元素收集会将剩余值放入一个新数组/对象中,不影响前面的解构。

掌握扩展与剩余运算符,你的代码将从繁琐的 concatsliceapply 中解放出来,变得更加简洁和可读。