rest参数和spread扩展运算符
在 JavaScript 中,rest 参数和spread 扩展运算符都使用...语法,但作用完全相反:rest 是“收集”元素,spread 是“展开”元素。下面详细说明两者的用法和区别。
一、rest 参数(收集剩余元素)
rest 参数(剩余参数)用于收集多个元素,聚合成一个数组,主要用于两种场景:函数参数列表、数组/对象解构。
1. 函数参数中的 rest 参数
当函数需要接收不确定数量的参数时,用...变量名定义 rest 参数,它会将传入的多余参数收集为一个数组。
特点:
- rest 参数必须是函数的最后一个参数(否则报错)。
- 收集的结果是真正的数组(可直接使用数组方法,如
map、reduce)。
示例:
求任意多个数字的和:
function sum(...nums) { // rest 参数 nums 收集所有传入的参数return nums.reduce((total, num) => total + num, 0);
}sum(1, 2); // 3(nums 是 [1,2])
sum(1, 2, 3, 4); // 10(nums 是 [1,2,3,4])
结合固定参数使用(rest 必须在最后):
function print(first, ...rest) { // first 接收第一个参数,rest 收集剩余参数console.log('第一个参数:', first);console.log('剩余参数:', rest);
}print('a', 'b', 'c', 'd');
// 第一个参数:a
// 剩余参数:['b', 'c', 'd']
2. 解构中的 rest 参数
在数组或对象解构时,用...变量名收集剩余未被解构的元素,聚合成数组或对象。
数组解构示例:
const [a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(b); // 2
console.log(rest); // [3,4,5](收集剩余元素)
对象解构示例:
const { name, age, ...rest } = { name: '张三', age: 18, gender: '男', height: 180 };
console.log(name); // 张三
console.log(age); // 18
console.log(rest); // { gender: '男', height: 180 }(收集剩余属性)
二、spread 扩展运算符(展开元素)
spread 扩展运算符(展开运算符)用于将可迭代对象(如数组、字符串、Set)或对象“拆成”单个元素,主要用于数组字面量、对象字面量、函数调用等场景。
1. 数组中的 spread 运算符
用于合并数组、复制数组(避免引用传递)、将类数组转为数组等。
示例 1:合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // 展开 arr1 和 arr2,再合并为新数组
console.log(merged); // [1,2,3,4]
示例 2:复制数组(深拷贝一层)
const original = [1, 2, 3];
const copy = [...original]; // 展开 original,生成新数组
copy.push(4);
console.log(original); // [1,2,3](原数组不受影响)
console.log(copy); // [1,2,3,4]
示例 3:将字符串拆分为数组
const str = 'hello';
const arr = [...str]; // 展开字符串的每个字符
console.log(arr); // ['h','e','l','l','o']
2. 函数调用中的 spread 运算符
用于将数组元素拆分为函数的独立参数(解决函数需要多个参数,但只有数组的场景)。
示例:
求数组中的最大值(Math.max()需要多个参数,而非数组):
const nums = [1, 3, 5, 2];
const max = Math.max(...nums); // 等价于 Math.max(1,3,5,2)
console.log(max); // 5
3. 对象中的 spread 运算符(ES2018 新增)
用于合并对象、复制对象(浅拷贝),会覆盖重复的属性。
示例 1:合并对象
const obj1 = { name: '张三', age: 18 };
const obj2 = { gender: '男', age: 20 }; // 重复属性 age
const merged = { ...obj1, ...obj2 }; // 展开并合并,后展开的覆盖前的
console.log(merged); // { name: '张三', age: 20, gender: '男' }
示例 2:复制对象(浅拷贝)
const original = { a: 1, b: { c: 2 } };
const copy = { ...original };
copy.b.c = 3; // 修改拷贝对象的嵌套属性
console.log(original.b.c); // 3(原对象的嵌套属性受影响,因为是浅拷贝)
三、核心区别
核心区别
| 类型 | 作用 | 场景 | 结果类型 |
|---|---|---|---|
| rest 参数 | 收集多个元素为数组/对象 | 函数参数、数组/对象解构 | 数组(函数/数组解构)、对象(对象解构) |
| spread 运算符 | 展开元素为单个值 | 数组字面量、对象字面量、函数调用 | 拆分为独立元素 |
其他区别
rest 参数和spread 扩展运算符虽然都使用...语法,但在使用位置、上下文要求、作用目标等语法层面有明确区别,核心差异如下:
1. 语法作用方向不同
-
rest 参数:是“收集”行为——将多个分散的元素“聚合”成一个数组或对象。
例如:function fn(...rest) {}中,...rest会把传入的多个参数收集为数组rest。 -
spread 扩展运算符:是“展开”行为——将一个聚合对象(如数组、对象)“拆分”成单个元素。
例如:[...arr]中,...arr会把数组arr的元素拆分成独立值,再组成新数组。
2. 使用位置与上下文限制不同
rest 参数的语法位置(严格限制):
只能用于两个场景,且必须作为“最后一个元素”出现:
-
函数参数列表的末尾:
必须是函数的最后一个参数,后面不能有其他参数,否则报错。
例:function fn(a, ...rest) {}(正确);function fn(...rest, a) {}(错误,语法报错)。 -
数组/对象解构的末尾:
在解构时,...变量名必须放在解构模式的最后,后面不能有其他解构项。
例:const [a, ...rest] = [1,2,3](正确);const [...rest, a] = [1,2,3](错误,语法报错);
例:const { x, ...rest } = {x:1, y:2, z:3}(正确);const {...rest, x} = {x:1, y:2}(错误)。
spread 扩展运算符的语法位置(灵活):
可用于多个场景,且可以在多个位置出现,无需作为最后一个元素:
-
数组字面量中:可在任意位置展开,甚至多次使用。
例:[1, ...arr, 3, ...arr2](正确,展开arr和arr2的元素)。 -
对象字面量中:可在任意位置展开,支持多次展开(后展开的属性会覆盖前面的重复属性)。
例:{ x:1, ...obj, y:2, ...obj2 }(正确)。 -
函数调用的参数中:可作为参数直接展开,位置灵活。
例:fn(1, ...arr, 3)(正确,等价于fn(1, arr[0], arr[1], ..., 3))。
3. 作用的目标与结果类型不同
-
rest 参数:
- 作用目标:函数接收的“多余参数”,或数组/对象中“未被解构的剩余元素”。
- 结果类型:
- 函数参数和数组解构中,结果是数组(即使没有元素,也是空数组
[])。 - 对象解构中,结果是对象(即使没有属性,也是空对象
{})。
- 函数参数和数组解构中,结果是数组(即使没有元素,也是空数组
-
spread 扩展运算符:
- 作用目标:可迭代对象(数组、字符串、Set、Map 等)或普通对象(ES2018 后支持)。
- 结果类型:本身不产生新类型,而是将目标“拆成”独立元素,用于构建新数组、新对象,或作为函数的独立参数。
总结:语法区别速查表
| 维度 | rest 参数 | spread 扩展运算符 |
|---|---|---|
| 核心行为 | 收集分散元素 → 聚合为数组/对象 | 拆分聚合对象 → 展开为独立元素 |
| 使用位置限制 | 必须作为最后一个元素(函数参数/解构末尾) | 无位置限制,可在多个位置出现 |
| 结果类型 | 数组(函数/数组解构)、对象(对象解构) | 无独立结果类型,仅用于展开元素 |
| 典型语法示例 | function fn(a, ...rest) {} | [...arr1, ...arr2]、fn(...args) |
简单记:
rest 是“聚”,spread 是“散”。
rest 是“收尾收集”,spread 是“中间/任意位置展开”。
