百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

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():...