JavaScript展开运算符和剩余运算符的区别详解


Posted in Javascript onFebruary 18, 2022

JavaScript使用符号三个点(...)作为剩余运算符和展开运算符,不过这两个运算符是有区别的。

最主要的区别就是,剩余运算符将用户提供的某些特定值的其余部分放入JavaScript数组中,而展开运算符则将可迭代的对象展开为单个元素。

例如下面这段代码,其中使用了剩余运算符将部分值存放到数组中:

// Use rest to enclose the rest of specific user-supplied values into an array:
function myBio(firstName, lastName, ...otherInfo) { 
  return otherInfo;
}

// Invoke myBio function while passing five arguments to its parameters:
myBio("Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male");

// The invocation above will return:
["CodeSweetly", "Web Developer", "Male"]

  查看运行结果

  上面的代码中,我们使用...otherInfo将传入函数myBio()参数中的剩余部分"CodeSweetly","We Developer"和"Male"存放到数组中。

  然后我们再来看下面这个例子,其中使用了展开运算符:

// Define a function with three parameters:
function myBio(firstName, lastName, company) { 
  return `${firstName} ${lastName} runs ${company}`;
}

// Use spread to expand an array's items into individual arguments:
myBio(...["Oluwatobi", "Sofela", "CodeSweetly"]);

// The invocation above will return:
“Oluwatobi Sofela runs CodeSweetly”

  查看运行结果

  上面的代码中,我们使用展开运算符(...)将数组["Oluwatobi", "Sofela", "CodeSweetly"]的内容逐一展开并传递给函数myBio()的参数。

  如果你对剩余运算符和展开运算符不是很熟悉,不用太担心,本文接下来会对它们进行介绍。

  在接下来的章节中,我们将详细讨论剩余运算符和展开运算符在JavaScript中是如何工作的。

什么是剩余运算符?

  正如上面所提到的,剩余运算符将用户提供的某些特定值的其余部分放入JavaScript数组中。语法如下:

...yourValues

  上面代码中的三个点(...)用来表示剩余运算符。

  剩余运算符之后的内容用来表示你希望填充到数组中的值。注意,只能在函数定义的最后一个参数中使用剩余运算符。

剩余运算符在JavaScript函数中是如何工作的?

  在JavaScript函数中,剩余运算符被用在函数最后一个参数的前面。如下面的代码:

// Define a function with two regular parameters and one rest parameter:
function myBio(firstName, lastName, ...otherInfo) { 
  return otherInfo;
}

  这里,剩余运算符告诉JavaScript程序将用户提供给参数otherInfo的任何值都添加到一个数组中。然后,将该数组赋值给otherInfo参数。

  因此,我们将...otherInfo称之为剩余参数。

  需要注意的是,调用时传递给函数的实参中剩余参数是可选的。

  另一个例子:

// Define a function with two regular parameters and one rest parameter:
function myBio(firstName, lastName, ...otherInfo) { 
  return otherInfo;
}

// Invoke myBio function while passing five arguments to its parameters:
myBio("Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male");

// The invocation above will return:
["CodeSweetly", "Web Developer", "Male"]

  查看运行结果

  上面的代码中,调用函数myBio()时传入了5个参数,而函数myBio()的实参只有3个。也就是说,参数"Oluwatobi"和"Sofela"分别传递给了firstName和lastName,其它的参数("CodeSweetly","Web Developer"和"Male")都传递给了剩余参数otherInfo。所以,函数myBio()返回的结果是["CodeSweetly", "Web Developer", "Male"],它们都是剩余参数otherInfo的内容。

注意!不能在包含剩余参数的函数体中使用"use strict"

  记住,不能在任何包含剩余参数,默认参数,或参数解构的函数体中使用"use strict"指令。否则,JavaScript将报语法错误。

  考虑下面的代码:

// Define a function with one rest parameter:
function printMyName(...value) {
  "use strict";
  return value;
}

// The definition above will return:
"Uncaught SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list"

  注意:只有当整个脚本或封闭作用域处于strict模式时,才可以将"use strict"放到函数体外。

  现在我们知道了剩余运算符在函数中的作用,接下来我们来看看它在参数解构中是如何工作的。

剩余运算符在参数解构中是如何工作的?

  剩余运算符在参数解构赋值时通常被用在最后一个变量的前面。下面是一个例子:

// Define a destructuring array with two regular variables and one rest variable:
const [firstName, lastName, ...otherInfo] = [
  "Oluwatobi", "Sofela", "CodeSweetly", "Web Developer", "Male"
];

// Invoke the otherInfo variable:
console.log(otherInfo); 

// The invocation above will return:
["CodeSweetly", "Web Developer", "Male"]

  查看运行结果

  这里的剩余运算符(...)告诉JavaScript程序将用户提供的其它值都添加到一个数组中。然后,将该数组赋值给otherInfo变量。

  因此,我们将这里的...otherInfo称之为剩余变量。

  另一个例子:

// Define a destructuring object with two regular variables and one rest variable:
const { firstName, lastName, ...otherInfo } = {
  firstName: "Oluwatobi",
  lastName: "Sofela", 
  companyName: "CodeSweetly",
  profession: "Web Developer",
  gender: "Male"
}

// Invoke the otherInfo variable:
console.log(otherInfo);

// The invocation above will return:
{companyName: "CodeSweetly", profession: "Web Developer", gender: "Male"}

  查看运行结果

  注意上面的代码中,剩余运算符将一个属性对象(而不是数组)赋值给otherInfo变量。也就是说,当你在解构一个对象时使用剩余运算符,它将生成一个属性对象而非数组。

  但是,如果你在解构数组或函数时使用剩余运算符,它将生成数组字面量。

  你应该已经注意到了,在JavaScript arguments和剩余参数之间存在一些区别,下面我们来看看这些区别都有哪些。

JavaScript arguments和剩余参数之间有哪些区别?

  下面我列出了JavaScript arguments和剩余参数之间的一些区别:

区别1:arguments对象是一个类似于数组的对象,但它并非真正的数组!
  请记住这一点,JavaScript arguments对象不是真正的数组。它是一个类似于数组的对象,不具备数组所拥有的任何特性。

  而剩余参数是一个真正的数组,你可以在它上面使用数组所拥有的任何方法。例如,你可以对一个剩余参数使用sort()、map()、forEach()或pop()方法。但你不能对arguments对象使用这些方法。

区别2:不能在箭头函数中使用arguments对象
  arguments对象在箭头函数中不可用,而剩余参数在所有的函数中都是可用的,也包括箭头函数。

区别3:优先使用剩余参数
  优先使用剩余参数而不是arguments对象,特别是在编写ES6兼容代码时。

  我们已经了解了剩余运算符是如何工作的,下面我们来讨论下展开运算符,并看看它和剩余运算符之间的区别。

什么是展开运算符以及它在JavaScript中是如何工作的?

  展开运算符(...)将一个可迭代的对象展开为单个元素。

  展开运算符可以应用在数组字面量,函数调用,以及被初始化的属性对象中,它将可迭代对象的值逐一展开到单独的元素中。实际上,它和剩余操作符正好相反。

  注意,只有在数组字面量,函数调用,或被初始化的属性对象中使用展开运算符时才有效。

  下面我们来看一些实际的例子。

示例1:展开运算符在数组字面量中如何工作

const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// The invocation above will return:
[ "Oluwatobi", "Sofela", "is", "my", "name." ]

  查看运行结果

  上面的代码中,展开运算符(...)将数组myName拷贝到aboutMe中。

  注意:

  • 对myName的任何修改不会反映到aboutMe中。因为myName数组中的所有值都是原语。扩展操作符只是简单地将myName数组的内容复制并粘贴到aboutMe中,而不创建任何对原始数组元素的引用。
  • 展开运算符只做浅拷贝。所以,当myName数组中包含任何非原语值时,JavaScript将在myName和aboutMe之间创建一个引用。有关展开运算符如何处理原语值和非原语值的更多信息,可以查看这里
  • 假设我们没有使用展开运算符来复制数组myName的内容,例如我们编写这行代码const aboutMe = [ "Oluwatobi", myName, "name." ] 这种情况下JavaScript将给myName分配一个引用,这样,所有对myName数组所做的修改就都会反映到aboutMe数组中。

示例2:如何使用展开运算符将字符串转换为数组

const myName = "Oluwatobi Sofela";

console.log([...myName]);

// The invocation above will return:
[ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]

  查看运行结果

  上面的代码中,我们在数组字面量中使用展开运算符([...])将myName字符串的值展开为一个数组。这样,字符串"Oluwatobi Sofela"的内容被展开到数组[ "O", "l", "u", "w", "a", "t", "o", "b", "i", " ", "S", "o", "f", "e", "l", "a" ]中。

示例3:展开运算符如何在函数调用中工作

const numbers = [1, 3, 5, 7];

function addNumbers(a, b, c, d) {
  return a + b + c + d;
}

console.log(addNumbers(...numbers));

// The invocation above will return:
16

  查看运行结果

  上面的代码中,我们使用展开运算符将数组numbers的内容展开并传递给函数addNumbers()的参数。如果数组numbers的元素多于4个,JavaScript只会将前4个元素作为参数传递给函数addNumbers()而忽略其余的元素。

  下面是一些其它的例子:

const numbers = [1, 3, 5, 7, 10, 200, 90, 59];

function addNumbers(a, b, c, d) {
  return a + b + c + d;
}

console.log(addNumbers(...numbers));

// The invocation above will return:
16

  查看运行结果

const myName = "Oluwatobi Sofela";

function spellName(a, b, c) {
  return a + b + c;
}

console.log(spellName(...myName));      // returns: "Olu"

console.log(spellName(...myName[3]));   // returns: "wundefinedundefined"

console.log(spellName([...myName]));    // returns: "O,l,u,w,a,t,o,b,i, ,S,o,f,e,l,aundefinedundefined"

console.log(spellName({...myName}));    // returns: "[object Object]undefinedundefined"

  查看运行结果 

示例4:展开运算符在对象字面量中如何工作

const myNames = ["Oluwatobi", "Sofela"];
const bio = { ...myNames, runs: "codesweetly.com" };

console.log(bio);

// The invocation above will return:
{ 0: "Oluwatobi", 1: "Sofela", runs: "codesweetly.com" }

  查看运行结果

  上面的代码中,我们在bio对象内部使用展开运算符将数组myNames的值展开为各个属性。

有关展开运算符我们需要知道的

  当使用展开运算符时,请记住以下三个基本信息。

1. 展开运算符不能展开对象字面量的值

  由于属性对象是非可迭代对象,所以不能使用展开运算符将它的值进行展开。但是,你可以使用展开运算符将一个对象的属性克隆到另一个对象中。

  看下面这个例子:

const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName, website: "codesweetly.com" };

console.log(bio);

// The invocation above will return:
{ firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com" };

  查看运行结果

  上面的代码中,我们使用展开运算符将myName对象的内容克隆到了bio对象中。

  注意:

  • 展开运算符只能展开可迭代对象的值。
  • 只有当一个对象包含一个带有@@iterator key的属性时,才是一个可迭代的对象。
  • Array,TypedArray,String,Map,Set都是内置的可迭代类型,因为它们默认都带有@@iterator属性。
  • 属性对象是非可迭代数组类型,因为默认情况下它没有@@iterator属性。
  • 可以在属性对象上添加@@iterator使其成为可迭代对象。

2. 展开运算符不克隆相同的属性

  假设我们使用展开运算符将对象A的属性克隆到对象B中,如果对象B包含与对象A中相同的属性,那么对象B的属性将覆盖对象A的属性。换句话说,在这个过程中,对象A中那些与对象B相同的属性不会被克隆。

  看下面这个例子:

const myName = { firstName: "Tobi", lastName: "Sofela" };
const bio = { ...myName, firstName: "Oluwatobi", website: "codesweetly.com" };

console.log(bio);

// The invocation above will return:
{ firstName: "Oluwatobi", lastName: "Sofela", website: "codesweetly.com" };

  查看运行结果

  注意,展开运算符没有将myName对象的firstName属性的值复制到bio对象中,因为对象bio中已经包含firstName属性了。

3. 注意展开运算符在包含非原语的对象中是何如工作的

  如果在只包含原语值的对象(或数组)上使用展开运算符,JavaScript不会在原对象和复制对象之间创建任何引用。

  看下面这段代码:

const myName = ["Sofela", "is", "my"];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// The invocation above will return:
["Oluwatobi", "Sofela", "is", "my", "name."]

  查看运行结果

  注意,myName中的每一个元素都是一个原语值,因此,当我们使用展开运算符将myName克隆到aboutMe时,JavaScript不会在两个数组之间创建任何引用。所以,对myName数组所做的任何修改都不会反映到aboutMe数组中,反之亦然。

  让我们给myName数组添加一个元素:

myName.push("real");

  现在我们来检查一下myName和aboutMe的值:

console.log(myName); // ["Sofela", "is", "my", "real"]

console.log(aboutMe); // ["Oluwatobi", "Sofela", "is", "my", "name."]

  请注意,我们对myName数组的修改并没有反映到aboutMe数组中,因为展开运算符并没有在原始数组和复制数组之间创建任何引用。

如果myName数组包含非原语项呢?

  假设myName包含非原语项,这种情况下,展开运算符将在原数组的非原语项和克隆项之间创建一个引用。

  看下面的例子:

const myName = [["Sofela", "is", "my"]];
const aboutMe = ["Oluwatobi", ...myName, "name."];

console.log(aboutMe);

// The invocation above will return:
[ "Oluwatobi", ["Sofela", "is", "my"], "name." ]

  查看运行结果

  注意,这里的myName数组包含一个非原语项。

  因此,当使用展开运算符将myName的内容克隆到aboutMe时,JavaScript将在两个数组之间创建一个引用。这样,任何对myName数组的修改都会反映到aboutMe数组中,反之亦然。

  作为例子,我们同样给myName数组添加一个元素:

myName[0].push("real");

  现在我们来查看myName和aboutMe的值:

console.log(myName); // [["Sofela", "is", "my", "real"]]

console.log(aboutMe); // ["Oluwatobi", ["Sofela", "is", "my", "real"], "name."]

  查看运行结果

  注意,对myName数组的修改内容反映到了aboutMe数组中,因为展开运算符在原始数组和复制数组之间创建了一个引用。

  另外一个例子:

const myName = { firstName: "Oluwatobi", lastName: "Sofela" };
const bio = { ...myName };

myName.firstName = "Tobi";

console.log(myName); // { firstName: "Tobi", lastName: "Sofela" }

console.log(bio); // { firstName: "Oluwatobi", lastName: "Sofela" }

  查看运行结果

  上面的代码中,myName的更新内容没有反映到bio对象中,因为我们在只包含原语值的对象上使用展开运算符。

  注意,开发人员通常将这里的myName称之为浅对象,因为它只包含原语项。

  另外一个例子:

const myName = { 
  fullName: { firstName: "Oluwatobi", lastName: "Sofela" }
};

const bio = { ...myName };

myName.fullName.firstName = "Tobi";

console.log(myName); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }

console.log(bio); // { fullName: { firstName: "Tobi", lastName: "Sofela" } }

  查看运行结果

  上面的代码中,myName的更新内容被反映到bio对象中,因为我们在包含非原语值的对象上使用了展开运算符。

  注意:

  • 我们称这里的myName为深对象,因为它包含非原语项。
  • 将一个对象克隆到另一个对象时,如果创建了引用,则进行的是浅拷贝。例如,...myName产生了myName对象的一个浅拷贝,因为你对其中一个对象所做的任何修改都会反映到另一个对象中。
  • 将一个对象克隆到另一个对象时,如果没有创建引用,则进行的时深拷贝。例如,我可以通过const bio = JSON.parse(JSON.stringify(myName))将myName对象深拷贝到bio对象中。如此一来,JavaScript将把myName克隆到bio中而不创建任何引用。
  • 我们可以通过用一个新对象来替换myName或bio中的fullName子对象,从而切断myName和bio之间的引用。例如,使用myName.fullName = { firstName: "Tobi", lastName: "Sofela" }来断开myName和bio之间的指针。

结语

  本文讨论了剩余操作符和展开操作符之间的区别,并通过示例说明了它们在JavaScript中是如何工作的。

到此这篇关于JavaScript展开运算符和剩余运算符的区别详解的文章就介绍到这了,更多相关JavaScript展开运算符和剩余运算符 内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
javascript CSS画图之基础篇
Jul 29 Javascript
JAVASCRIPT实现的WEB页面跳转以及页面间传值方法
May 13 Javascript
20个最新的jQuery插件
Jan 13 Javascript
jQuery之$(document).ready()使用介绍
Apr 05 Javascript
QQ空间顶部折页撕开效果示例代码
Jun 15 Javascript
Javascript实现检测客户端类型代码封包
Dec 03 Javascript
jQuery EasyUI框架中的Datagrid数据表格组件结构详解
Jun 09 Javascript
Vue 过渡实现轮播图效果
Mar 27 Javascript
ES6新数据结构Set与WeakSet用法分析
Mar 31 Javascript
简单快速的实现js计算器功能
Aug 17 Javascript
在vue中使用echarts图表实例代码详解
Oct 22 Javascript
vue实现登录功能
Dec 31 Vue.js
微信小程序中使用vant框架的具体步骤
Vue elementUI表单嵌套表格并对每行进行校验详解
Feb 18 #Vue.js
微信小程序中wxs文件的一些妙用分享
Feb 18 #Javascript
vue项目支付功能代码详解
Feb 18 #Vue.js
JavaScript的Set数据结构详解
Feb 18 #Javascript
JS封装cavans多种滤镜组件
HTML+JS实现在线朗读器
Feb 15 #Javascript
You might like
Windows下PHP的任意文件执行漏洞
2006/10/09 PHP
MySQL授权问题总结
2007/05/06 PHP
CodeIgniter视图使用注意事项
2016/01/20 PHP
PHP动态地创建属性和方法, 对象的复制, 对象的比较,加载指定的文件,自动加载类文件,命名空间
2016/05/06 PHP
php实现查询功能(数据访问)
2017/05/23 PHP
PHP实现微信小程序人脸识别刷脸登录功能
2018/05/24 PHP
js如何判断不同系统的浏览器类型
2013/10/28 Javascript
jQuery实现可编辑的表格实例讲解(2)
2015/09/17 Javascript
JS基于面向对象实现的拖拽库实例
2015/09/24 Javascript
js如何实现淡入淡出效果
2020/11/18 Javascript
JS实现的表格操作类详解(添加,删除,排序,上移,下移)
2015/12/22 Javascript
JS中with的替代方法与String中的正则方法详解
2016/12/23 Javascript
JavaScript获取短信验证码(周期性)
2016/12/29 Javascript
jQuery插件zTree实现更新根节点中第i个节点名称的方法示例
2017/03/08 Javascript
深入理解nodejs中Express的中间件
2017/05/19 NodeJs
easyui下拉框动态级联加载的示例代码
2017/11/29 Javascript
javascript判断一个变量是数组还是对象
2019/04/10 Javascript
微信小程序和百度的语音识别接口详解
2019/05/06 Javascript
PYTHON 中使用 GLOBAL引发的一系列问题
2016/10/12 Python
Python实现识别手写数字大纲
2018/01/29 Python
Python实现的文轩网爬虫完整示例
2019/05/16 Python
python实现列表中最大最小值输出的示例
2019/07/09 Python
DJANGO-URL反向解析REVERSE实例讲解
2019/10/25 Python
python取均匀不重复的随机数方式
2019/11/27 Python
Python基于Socket实现简易多人聊天室的示例代码
2020/11/29 Python
python drf各类组件的用法和作用
2021/01/12 Python
HTML5之web workers_动力节点Java学院整理
2017/07/17 HTML / CSS
美国全球旅游运营商:Pacific Holidays
2018/06/18 全球购物
办公室年终个人自我评价
2013/10/28 职场文书
《哪吒闹海》教学反思
2014/02/28 职场文书
2015年银行员工工作总结
2015/04/24 职场文书
学校隐患排查制度
2015/08/05 职场文书
java项目构建Gradle的使用教程
2022/03/24 Java/Android
win10更新失败无限重启解决方法
2022/04/19 数码科技
MYSQL如何查看操作日志详解
2022/05/30 MySQL
V Rising 服务器搭建图文教程
2022/06/16 Servers