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 相关文章推荐
线路分流自动跳转代码;希望对大家有用!
Dec 02 Javascript
alixixi runcode.asp的代码不错的应用
Aug 08 Javascript
定义JavaScript二维数组采用定义数组的数组来实现
Dec 09 Javascript
node中socket.io的事件使用详解
Dec 15 Javascript
基于jQuery实现响应式圆形图片轮播特效
Nov 25 Javascript
JavaScript基于replace+正则实现ES6的字符串模版功能
Apr 25 Javascript
jQuery中的for循环var与let的区别
Apr 21 jQuery
浅谈如何使用webpack构建多页面应用
May 30 Javascript
vue+axios 前端实现登录拦截的两种方式(路由拦截、http拦截)
Oct 24 Javascript
Vuex 模块化使用详解
Jul 31 Javascript
vue滑动吸顶及锚点定位的示例代码
May 10 Javascript
Openlayers实现地图的基本操作
Sep 28 Javascript
微信小程序中使用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
PHP学习笔记(一) 简单了解PHP
2014/08/04 PHP
Zend Framework教程之Resource Autoloading用法实例
2016/03/08 PHP
jquery使用jquery.zclip插件复制对象的实例教程
2013/12/04 Javascript
关于Javascript加载执行优化的研究报告
2014/12/16 Javascript
js控制元素显示在屏幕固定位置及监听屏幕高度变化的方法
2015/08/11 Javascript
JS实现获取键盘按下的按键并显示在页面上的方法
2015/11/04 Javascript
javascript解析ajax返回的xml和json格式数据实例详解
2017/01/05 Javascript
node.js中express-session配置项详解
2017/05/31 Javascript
通过js控制时间,一秒一秒自己动的实例
2017/10/25 Javascript
vue+webpack实现异步加载三种用法示例详解
2018/04/24 Javascript
element-ui 本地化使用教程详解
2019/10/28 Javascript
jquery实现进度条状态展示
2020/03/26 jQuery
vue:el-input输入时限制输入的类型操作
2020/08/05 Javascript
Vue+Vant 图片上传加显示的案例
2020/11/03 Javascript
Python实现1-9数组形成的结果为100的所有运算式的示例
2017/11/03 Python
解决PyCharm不运行脚本,而是运行单元测试的问题
2019/01/17 Python
使用python实现mqtt的发布和订阅
2019/05/05 Python
关于Python核心框架tornado的异步协程的2种方法详解
2019/08/28 Python
Python数据结构dict常用操作代码实例
2020/03/12 Python
基于html5 canvas做批改作业的小插件
2020/05/20 HTML / CSS
BASIC HOUSE官方旗舰店:韩国著名的服装品牌
2018/09/27 全球购物
巴黎卡诗加拿大官网:Kérastase加拿大
2018/11/12 全球购物
mysql的最长数据库名,表名,字段名可以是多长
2014/04/21 面试题
物业工作计划书
2014/01/10 职场文书
党章学习思想汇报
2014/01/14 职场文书
应届毕业生应聘自荐信范文
2014/02/26 职场文书
创建无烟单位实施方案
2014/03/29 职场文书
本科毕业生求职自荐信
2014/04/09 职场文书
1亿有多大教学反思
2014/05/01 职场文书
陈胜吴广起义口号
2014/06/20 职场文书
社区服务活动小结
2014/07/08 职场文书
考研复习计划
2015/01/19 职场文书
倡议书格式及范文
2015/04/29 职场文书
志愿者工作心得体会
2016/01/15 职场文书
2019自荐信范文集锦!
2019/07/03 职场文书
win server2012 r2服务器共享文件夹如何设置
2022/06/21 Servers