讲解
三个点 ... 在 JavaScript 里有两个相反方向的用途,看位置区分:用在「收集」位置(函数参数列表、解构模式左边)叫剩余语法,把零散的值收成数组或对象;用在「摊开」位置(函数调用、数组/对象字面量里)叫展开语法,把数组或对象摊开成零散的值。rest 是把多合一,spread 是把一拆多。
展开语法的日常用途:数组合并 [...a, ...b]、数组复制 [...arr]、把类数组/可迭代对象转真数组 [...nodeList]、函数调用时把数组展开成参数列表 Math.max(...nums)(取代老的 apply)。对象展开 { ...obj } 做浅拷贝和合并,后者属性覆盖前者:{ ...defaults, ...options } 是配置合并的标准写法。注意展开只做浅层处理,嵌套结构仍共享引用。
剩余语法的日常用途:函数签名 function sum(...nums) 收集任意多个实参(真数组,取代 arguments);解构里 const [first, ...rest] = arr 拿走第一个留下其余;const { id, ...others } = obj 排除某些键。剩余参数必须是参数列表的最后一个,一个函数也只能有一个剩余参数。
示例
// 展开:数组合并与复制
const a = [1, 2];
const b = [3, 4];
console.log('合并:', [...a, ...b]);
const copy = [...a];
copy.push(99);
console.log('复制互不影响:', a, copy);
// 展开:把数组摊成参数
const nums = [3, 9, 5];
console.log('最大值:', Math.max(...nums));
// 展开:可迭代对象转数组
console.log('字符串摊开:', [...'你好']);
console.log('Set 转数组:', [...new Set([1, 1, 2, 2, 3])]);
// 对象展开:浅拷贝与合并覆盖
const defaults = { theme: 'light', fontSize: 14, lang: 'zh' };
const userConfig = { fontSize: 18 };
console.log('合并:', { ...defaults, ...userConfig });
// 剩余:收集任意参数
function sum(...values) {
return values.reduce((acc, v) => acc + v, 0);
}
console.log('求和:', sum(1, 2, 3, 4));
// 剩余:解构收尾
const [first, ...rest] = [1, 2, 3, 4];
const { x, ...others } = { x: 1, y: 2, z: 3 };
console.log(first, rest, others);
常见坑
- 以为展开是深拷贝:[...arr] 和 { ...obj } 都只处理第一层,嵌套数组/对象依然共享引用,改嵌套会互相影响。
- 对象展开丢原型:{ ...instance } 得到的是普通对象,class 实例的方法不会跟着过来。
- 剩余参数位置错误:function f(...args, last) 是语法错误,剩余参数必须收尾。
- 展开大数组当参数:Math.max(...十万级数组) 会触发「参数过多」的栈错误,超大数据用 reduce 代替。
小结
... 在收集位置是剩余、在摊开位置是展开;展开做合并/拷贝/转数组,剩余做参数收集/解构收尾,都是浅层操作。下一节学习字符串的现代写法:模板字符串。