JavaScript展开运算符与剩余参数,灵活操作数组与对象的终极利器
myzbx 2025-07-03 18:16 15 浏览
各位IT界的探索者们,你是否在日常编码中遇到过这样的场景:想将两个数组合并,或者给一个对象添加几个新属性,却不得不写一堆重复冗余的代码?又或者,你写了一个函数,希望它能接收任意数量的参数,却发现传统的arguments对象用起来那么不优雅、甚至有点“过时”?在JavaScript的现代世界里,这些曾经的“小烦恼”,如今都有了优雅且强大的解决方案。
今天,我们就来揭秘JavaScript中一对形影不离、却又各司其职的“魔法双子星”——展开运算符(Spread Operator) 和 剩余参数(Rest Parameters)。它们乍一看长得一模一样,都是那三个点...,但在不同的语境下,它们却能分别扮演着“打开包装”和“打包零散”的截然不同角色。它们不仅能让你的代码变得更简洁、更富有表现力,还能让你在处理数组和对象时拥有前所未有的灵活性。准备好了吗?让我们一起探索这对“天生一对”的利器,看看它们是如何彻底改变你写代码的方式的!
一、它们是什么?——长得像,但功能“南辕北辙”!
在深入学习它们各自的“魔法”之前,我们得先搞清楚这对“双胞胎”的本质区别。虽然都长着...这副面孔,但它们的职责却截然不同:
- 展开运算符(Spread Operator): 顾名思义,它的作用是**“展开”**。它将一个可迭代对象(如数组、字符串)的元素在原地“展开”成一个个独立的个体,或者将一个对象的属性“展开”成一个个独立的键值对。你可以想象成把一个“包装盒”拆开,里面的内容全部散落出来。
- 剩余参数(Rest Parameters): 与“展开”相反,它的作用是**“收集”**。它将函数调用时传入的、数量不定的多个参数,或者数组/对象解构时“剩余”的部分,收集到一个数组中。这就像是把零散的东西“打包”成一个整体。
划重点: 展开是“分”,剩余是“合”。理解了这一点,你就掌握了这对“双子星”的精髓!
二、展开运算符(Spread Operator):“打开包装”,释放强大能量!
展开运算符让数据操作变得异常灵活和简洁,它主要应用在以下几个场景:
1. 数组的“拷贝”与“合并”
在过去,拷贝数组通常需要slice()方法,合并数组需要concat()方法。有了展开运算符,一切变得如此优雅:
const originalArray = [1, 2, 3];
// 拷贝数组:创建一个新数组,包含原数组的所有元素(浅拷贝)
const copiedArray = [...originalArray];
console.log(copiedArray); // [1, 2, 3]
console.log(copiedArray === originalArray); // false,是新数组!
// 合并数组:将多个数组的元素展开,组合成一个新数组
const arrayA = [1, 2];
const arrayB = [3, 4];
const combinedArray = [...arrayA, ...arrayB, 5, 6];
console.log(combinedArray); // [1, 2, 3, 4, 5, 6]
// 插入元素:在数组中间插入新元素
const parts = [1, 5];
const newArr = [2, ...parts, 3, 4];
console.log(newArr); // [2, 1, 5, 3, 4]
对比传统方法,是不是简洁了不止一星半点?这种非破坏性操作(不改变原数组)在函数式编程和响应式框架中尤其重要。
2. 对象的“拷贝”与“合并”
展开运算符也能完美地应用于对象,实现对象的浅拷贝和属性合并:
const userProfile = {
name: '张三',
age: 30,
city: '北京'
};
// 拷贝对象:创建一个新对象,包含原对象的所有属性
const copiedProfile = { ...userProfile };
console.log(copiedProfile); // { name: '张三', age: 30, city: '北京' }
console.log(copiedProfile === userProfile); // false
// 合并对象:将多个对象的属性展开,组合成一个新对象
const defaultSettings = { theme: 'dark', notifications: true };
const userSettings = { notifications: false, language: 'zh-CN' };
// 后面的属性会覆盖前面的同名属性
const finalSettings = { ...defaultSettings, ...userSettings };
console.log(finalSettings);
// { theme: 'dark', notifications: false, language: 'zh-CN' }
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
这对于更新状态(如React/Vue组件中的state),或者合并配置对象非常实用。它让你的更新操作变得异常清晰,并且保持了数据的不可变性。
3. 将可迭代对象转换为参数列表
展开运算符的另一个强大之处在于,它能将一个可迭代对象(如数组)的元素“展开”为函数调用的独立参数:
const numbers = [10, 20, 5, 30, 15];
// 找出数组中的最大值,无需使用循环
const maxNumber = Math.max(...numbers);
console.log(maxNumber); // 30
// 将字符串展开为数组的元素
const greeting = 'Hello';
const chars = [...greeting];
console.log(chars); // ['H', 'e', 'l', 'l', 'o']
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
是不是感觉像“魔法”一样?原本需要循环遍历或特定方法才能完成的操作,现在一行代码就能搞定!
三、剩余参数(Rest Parameters):“打包零散”,聚合为一!
剩余参数如同一个“收集器”,它将函数调用时多余的参数,或者解构时未匹配的元素/属性,优雅地收集起来。
1. 函数参数的“收集器”
这是剩余参数最常见的应用场景。它能让你在函数中接收任意数量的参数,并以数组的形式进行处理,彻底告别了“古老”且有些别扭的arguments对象。
// 接收任意数量的数字并求和
function sum(...nums) { // nums是一个数组
let total = 0;
for (const num of nums) {
total += num;
}
return total;
}
console.log(sum(1, 2)); // 3
console.log(sum(1, 2, 3, 4, 5)); // 15
// 结合普通参数
function greet(firstName, lastName, ...hobbies) {
console.log(`你好,${firstName} ${lastName}!`);
if (hobbies.length > 0) {
console.log(`你的爱好有:${hobbies.join('、')}`);
}
}
greet('李', '明', '阅读', '跑步', '编程');
// 输出:
// 你好,李 明!
// 你的爱好有:阅读、跑步、编程
greet('王', '芳');
// 输出:你好,王 芳!
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
看到没?...nums就负责把所有传入的参数“打包”成一个名为nums的数组,让函数处理不定数量参数变得如此简洁和直观。
2. 解构赋值中的“收集器”
剩余参数也可以用在数组或对象的解构赋值中,用来收集“剩下”的元素或属性。
// 数组解构中的剩余参数
const [first, second, ...remainingNumbers] = [10, 20, 30, 40, 50];
console.log(first); // 10
console.log(second); // 20
console.log(remainingNumbers); // [30, 40, 50]
// 对象解构中的剩余参数(注意:剩余参数必须是解构的最后一个属性)
const { name, age, ...otherDetails } = {
name: '小A',
age: 28,
occupation: '前端工程师',
hometown: '广州'
};
console.log(name); // '小A'
console.log(age); // 28
console.log(otherDetails); // { occupation: '前端工程师', hometown: '广州' }
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
这对于从一个复杂的数据结构中取出部分关键信息,同时又想保留其余部分以便后续处理的场景,简直是神器!
四、为什么它们是“天生一对”?——“分”与“合”的艺术!
展开运算符和剩余参数虽然功能相反,但它们常常配合使用,形成一种强大的“分”与“合”的编程模式,尤其在处理不可变数据时大放异彩。
场景一:函数传递参数的完美配合
一个函数用剩余参数接收不定数量的参数,然后将这些参数再用展开运算符传递给另一个函数。
function logArgs(...args) { // 使用剩余参数收集所有传入的参数
console.log('所有参数:', args);
anotherFunction(...args); // 使用展开运算符将收集到的参数再次展开传递
}
function anotherFunction(p1, p2, p3, ...rest) {
console.log('另一个函数接收到:', p1, p2, p3, rest);
}
logArgs(1, 'hello', true, { id: 1 }, [2, 3]);
// 输出:
// 所有参数: [1, 'hello', true, { id: 1 }, [2, 3]]
// 另一个函数接收到: 1 hello true [{ id: 1 }, [2, 3]]
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
这种模式让函数的参数传递变得极其灵活,不再受固定参数数量的限制。
场景二:非破坏性地更新数组或对象
这是它们在React/Vue等现代前端框架中被大量使用的原因。通过展开运算符创建一个新数组/对象,同时添加、修改或删除元素/属性,而不会直接修改原始数据。
const todos = [
{ id: 1, text: '学习JS', completed: false },
{ id: 2, text: '编写博客', completed: true }
];
// 添加一个新任务
const newTodo = { id: 3, text: '发布文章', completed: false };
const updatedTodosAdd = [...todos, newTodo];
console.log('添加后:', updatedTodosAdd);
// 标记任务已完成(通过映射并创建新对象)
const updatedTodosComplete = todos.map(todo =>
todo.id === 1 ? { ...todo, completed: true } : todo // 使用展开运算符更新属性
);
console.log('更新后:', updatedTodosComplete);
// 删除一个任务
const updatedTodosDelete = todos.filter(todo => todo.id !== 1);
console.log('删除后:', updatedTodosDelete);
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
这种模式极大地提升了代码的可维护性和可预测性,因为它避免了副作用,让数据流向更加清晰。
五、优势与最佳实践:让代码“会呼吸”
- 代码简洁性: 显著减少了冗余代码,尤其是数组和对象的合并、拷贝操作。
- 可读性提升: 语义清晰,一眼就能看出是在“展开”还是在“收集”,意图明确。
- 促进不可变性: 尤其是在处理数组和对象时,它鼓励创建新的数据副本而不是直接修改原始数据,这对于React、Redux、Vuex等状态管理模式至关重要。
- 替代老旧方法: 告别arguments对象、Array.prototype.concat、Object.assign()等,用更现代、更直观的方式实现相同功能。
小提醒: 展开运算符进行的数组和对象拷贝都是浅拷贝。这意味着如果你的数组或对象中嵌套了其他对象或数组,那么内层的数据仍然是引用关系。如果需要深拷贝,你可能需要考虑JSON.parse(JSON.stringify(obj))(有局限性)或使用专门的深拷贝库(如Lodash的cloneDeep)。
总结:拥抱“双子星”,驾驭现代JavaScript!
展开运算符和剩余参数,这对看起来相似却功能相反的“魔法双子星”,是现代JavaScript开发中不可或缺的利器。它们极大地提升了我们处理数组和对象的灵活性,简化了函数参数的处理,并促进了更健壮、更可预测的编程范式(尤其是不可变性)。
从今天开始,尝试在你的代码中多思考:这里是需要“展开”数据,还是需要“收集”数据?当你熟练掌握了它们的用法,你会发现自己的JavaScript代码变得更加简洁、高效、富有表现力,仿佛拥有了“点石成金”的能力!
那么,在你的日常开发中,你最常用的是展开运算符还是剩余参数?它们又在哪些场景下给你带来了惊喜呢?欢迎在评论区分享你的经验和看法,我们一起进步,让代码“会呼吸”!
相关推荐
- 半导体行业术语缩写词典总结-JKL_半导体词汇缩写表
-
作为半导体行业新人来说,最痛苦的莫过于各种缩写词术语了,有的缩写词一样但是会有不同的解释。这里作者给大家整理了部分术语词典,后面会按照更新顺序一一分享出来。废话不多说,直接开始,如有遗漏,欢迎大家在评...
- JD.com Deepens Push Into Embodied Intelligence With Investment in Sensor Maker PaXiniTech
-
ToraOne,thesecond-generationmultidimensionaltactilehumanoidrobotdevelopedbyPaXiniTechTMTPOS...
- Hong Kong's Consumer Market Becomes New Battleground for Chinese Mainland Internet Giants
-
AI-generatedimageTMTPOST--StrollthroughthestreetsofHongKongtoday,anditmightfeellikey...
- http2解决了哪些问题_简述http2的优点
-
HTTP/2(最初称为SPDY)是HTTP协议的第二个主要版本,它在HTTP/1.1的基础上进行了重大改进,旨在解决其在性能和效率方面的诸多瓶颈。以下是HTTP/2主要解决的问题:队头阻...
- China's economy stays strong and vital amid pressure
-
Peoplevisitthe4thChina-CEECExpo&InternationalConsumerGoodsFairinNingbo,eastChina's...
- JD.com Makes $2.4 Billion Bid for Ceconomy in Bold Push to Build a Global Retail Empire
-
TMTPOST--JD.comhasunveiledplanstoacquireGermany’sCeconomyAG—theparentofEurope’sleading...
- 深入剖析 Java 中的装饰器设计模式:原理、应用与实践
-
在Java软件开发的广阔天地里,设计模式犹如璀璨星辰,照亮我们构建高效、可维护系统的道路。今天,让我们聚焦于其中一颗闪耀的星——装饰器设计模式,深入探究它的奥秘,看看如何利用它为我们的代码赋予...
- 组合模式应用-适配器模式_适配器组件
-
写在前面Hello,我是易元,这篇文章是我学习设计模式时的笔记和心得体会。如果其中有错误,欢迎大家留言指正!该部分为各模式组合使用,涉及代码较多,熟能生巧。内容回顾定义适配器模式是一种结构型设计模式,...
- OOM (Out Of Memory) 故障排查指南
-
1.确认OOM类型首先需要确认是哪种类型的OOM:JavaHeapOOM:Java堆内存不足NativeMemoryOOM:本地内存不足MetaspaceOOM:元空间内存不足Contai...
- 刷完这49题,面试官当场给Offer!Java程序员必备指南
-
1.问题:如果main方法被声明为private会怎样?答案:能正常编译,但运行的时候会提示”main方法不是public的”。2.问题:Java里的传引用和传值的区别是什么?答案:传引用是指传递的是...
- C#编程基础(看这一篇就够了)_c#编程入门与应用
-
C#及其开发环境简介C#概述C#是一个现代的、通用的、面向对象的编程语言,由微软(Microsoft)开发,经Ecma和ISO核准认可。它由AndersHejlsberg和他的团队在.NET框架开发...
- 说一下JDK的监控和 线上处理的一些case
-
一句话总结JDK监控常用工具包括JConsole、VisualVM、JMC等,用于实时查看内存、线程、GC状态。线上常见问题处理:内存泄漏通过heapdump分析对象引用链;频繁GC可调整-Xmx/...
- JavaScript深拷贝极简指南:3种方法解决嵌套与循环引用难题
-
为什么需要深拷贝?首先我们看看浅拷贝,point指向的是同一个地址,这时我们修改obj2.point的属性时,obj1的point属性也会被修改再看看深拷贝,point指向的是不同地址,这时我们修改o...
- Java 25 在 JEP 519 中集成了紧凑对象头
-
作者|ANMBazlurRahman译者|刘雅梦策划|丁晓昀Java25通过JEP519将紧凑对象头作为产品特性进行了集成,在不需要更改任何代码的情况下,为开发人员提供了...
- 每日一练 Python 面试题(1)_python每日一记
-
以下是5道Python基本语法相关的面试题,涵盖变量、运算符、数据结构、函数和异常处理等核心概念:1.变量与作用域题目:以下代码的输出是什么?解释原因。x=10deffunc():...
- 一周热门
- 最近发表
-
- 半导体行业术语缩写词典总结-JKL_半导体词汇缩写表
- JD.com Deepens Push Into Embodied Intelligence With Investment in Sensor Maker PaXiniTech
- Hong Kong's Consumer Market Becomes New Battleground for Chinese Mainland Internet Giants
- http2解决了哪些问题_简述http2的优点
- China's economy stays strong and vital amid pressure
- JD.com Makes $2.4 Billion Bid for Ceconomy in Bold Push to Build a Global Retail Empire
- 深入剖析 Java 中的装饰器设计模式:原理、应用与实践
- 组合模式应用-适配器模式_适配器组件
- OOM (Out Of Memory) 故障排查指南
- 刷完这49题,面试官当场给Offer!Java程序员必备指南
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)