Rest 参数与 Spread 语法

在 JavaScript 中,很多内建函数都支持传入任意数量的参数。

例如:

  • Math.max(arg1, arg2, ..., argN) —— 返回入参中的最大值。
  • Object.assign(dest, src1, ..., srcN) —— 依次将属性从 src1..N 复制到 dest
  • ……等。

在本文中,我们将学习如何编程实现支持函数可传入任意数量的参数。以及,如何将数组作为参数传递给这类函数。

Rest 参数 ...

在 JavaScript 中,无论函数是如何定义的,你都可以使用任意数量的参数调用函数。

例如:

function sum(a, b) {  return a + b;}

alert( sum(1, 2, 3, 4, 5) );

虽然这里不会因为传入“过多”的参数而报错。但是当然,在结果中只有前两个参数被计算进去了。

Rest 参数可以通过使用三个点 ... 并在后面跟着包含剩余参数的数组名称,来将它们包含在函数定义中。这些点的字面意思是“将剩余参数收集到一个数组中”。

例如,我们需要把所有的参数都放到数组 args 中:

function sumAll(...args) { // 数组名为 args  let sum = 0;

  for (let arg of args) sum += arg;

  return sum;}

alert( sumAll(1) ); // 1alert( sumAll(1, 2) ); // 3alert( sumAll(1, 2, 3) ); // 6

我们也可以选择获取第一个参数作为变量,并将剩余的参数收集起来。

下面的例子把前两个参数定义为变量,并把剩余的参数收集到 titles 数组中:

function showName(firstName, lastName, ...titles) {  alert( firstName + ' ' + lastName ); // Julius Caesar

  // 剩余的参数被放入 titles 数组中  // i.e. titles = ["Consul", "Imperator"]  alert( titles[0] ); // Consul  alert( titles[1] ); // Imperator  alert( titles.length ); // 2}

showName("Julius", "Caesar", "Consul", "Imperator");

Rest 参数必须放到参数列表的末尾

Rest 参数会收集剩余的所有参数,因此下面这种用法没有意义,并且会导致错误:

function f(arg1, ...rest, arg2) { // arg2 在 ...rest 后面?!  // error}

...rest 必须处在最后。

"arguments" 变量

有一个名为 arguments 的特殊的类数组对象,该对象按参数索引包含所有参数。

例如:

function showName() {  alert( arguments.length );  alert( arguments[0] );  alert( arguments[1] );

  // 它是可遍历的  // for(let arg of arguments) alert(arg);}

// 依次显示:2,Julius,CaesarshowName("Julius", "Caesar");

// 依次显示:1,Ilya,undefined(没有第二个参数)showName("Ilya");

在过去,JavaScript 中没有 rest 参数,而使用 arguments 是获取函数所有参数的唯一方法。现在它仍然有效,我们可以在一些老代码里找到它。

但缺点是,尽管 arguments 是一个类数组,也是可迭代对象,但它终究不是数组。它不支持数组方法,因此我们不能调用 arguments.map(...) 等方法。

此外,它始终包含所有参数,我们不能像使用 rest 参数那样只截取入参的一部分。

因此,当我们需要这些功能时,最好使用 rest 参数。

箭头函数是没有 "arguments"

如果我们在箭头函数中访问 arguments,访问到的 arguments 并不属于箭头函数,而是属于箭头函数外部的“普通”函数。

举个例子:

function f() {  let showArg = () => alert(arguments[0]);  showArg();}

f(1); // 1

我们已经知道,箭头函数没有自身的 `this`。现在我们知道了它们也没有特殊的 `arguments` 对象。

Spread 语法

我们刚刚看到了如何从参数列表中获取数组。

不过有时候我们也需要做与之相反的事儿。

例如,内建函数 Math.max[1] 会返回参数中最大的值:

alert( Math.max(3, 5, 1) ); // 5

假如我们有一个数组 [3, 5, 1],我们该如何用它调用 Math.max 呢?

直接把数组“原样”传入是不会奏效的,因为 Math.max 希望你传入一个列表形式的数值型参数,而不是一个数组:

let arr = [3, 5, 1];

alert( Math.max(arr) ); // NaN

毫无疑问,我们不可能手动地去一一设置参数 Math.max(arg[0], arg[1], arg[2]),因为我们不确定这儿有多少个。在脚本执行时,可能参数数组中有很多个元素,也可能一个都没有。并且这样设置的代码也很丑。

Spread 语法 来帮助你了!它看起来和 rest 参数很像,也使用 ...,但是二者的用途完全相反。

当在函数调用中使用 ...arr 时,它会把可迭代对象 arr “展开”到参数列表中。

Math.max 为例:

let arr = [3, 5, 1];

alert( Math.max(...arr) ); // 5(spread 语法把数组转换为参数列表)

我们还可以通过这种方式传递多个可迭代对象:

let arr1 = [1, -2, 3, 4];let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) ); // 8

我们甚至还可以将 spread 语法与常规值结合使用:

let arr1 = [1, -2, 3, 4];let arr2 = [8, 3, -8, 1];

alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25

并且,我们还可以使用 spread 语法来合并数组:

let arr = [3, 5, 1];let arr2 = [8, 9, 15];

let merged = [0, ...arr, 2, ...arr2];

alert(merged); // 0,3,5,1,2,8,9,15(0,然后是 arr,然后是 2,然后是 arr2)

在上面的示例中,我们使用数组展示了 spread 语法,其实任何可迭代对象都可以。

例如,在这儿我们使用 spread 语法将字符串转换为字符数组:

let str = "Hello";

alert( [...str] ); // H,e,l,l,o

Spread 语法内部使用了迭代器来收集元素,与 for..of 的方式相同。

因此,对于一个字符串,for..of 会逐个返回该字符串中的字符,...str 也同理会得到 "H","e","l","l","o" 这样的结果。随后,字符列表被传递给数组初始化器 [...str]

对于这个特定任务,我们还可以使用 Array.from 来实现,因为该方法会将一个可迭代对象(如字符串)转换为数组:

let str = "Hello";

// Array.from 将可迭代对象转换为数组alert( Array.from(str) ); // H,e,l,l,o

运行结果与 [...str] 相同。

不过 Array.from(obj)[...obj] 存在一个细微的差别:

  • Array.from 适用于类数组对象也适用于可迭代对象。
  • Spread 语法只适用于可迭代对象。

因此,对于将一些“东西”转换为数组的任务,Array.from 往往更通用。

获取一个 array/object 的副本

还记得我们 之前讲过的[2]Object.assign() 吗?

使用 spread 语法也可以做同样的事情。

let arr = [1, 2, 3];let arrCopy = [...arr]; // 将数组 spread 到参数列表中                        // 然后将结果放到一个新数组

// 两个数组中的内容相同吗?alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true

// 两个数组相等吗?alert(arr === arrCopy); // false(它们的引用是不同的)

// 修改我们初始的数组不会修改副本:arr.push(4);alert(arr); // 1, 2, 3, 4alert(arrCopy); // 1, 2, 3

并且,也可以通过相同的方式来复制一个对象:

let obj = { a: 1, b: 2, c: 3 };let objCopy = { ...obj }; // 将对象 spread 到参数列表中                          // 然后将结果返回到一个新对象

// 两个对象中的内容相同吗?alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true

// 两个对象相等吗?alert(obj === objCopy); // false (not same reference)

// 修改我们初始的对象不会修改副本:obj.d = 4;alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}

这种方式比使用 let arrCopy = Object.assign([], arr); 来复制数组,或使用 let objCopy = Object.assign({}, obj); 来复制对象写起来要短得多。因此,只要情况允许,我们更喜欢使用它。

总结

当我们在代码中看到 "..." 时,它要么是 rest 参数,要么就是 spread 语法。

有一个简单的方法可以区分它们:

  • ... 出现在函数参数列表的最后,那么它就是 rest 参数,它会把参数列表中剩余的参数收集到一个数组中。
  • ... 出现在函数调用或类似的表达式中,那它就是 spread 语法,它会把一个数组展开为列表。

使用场景:

  • Rest 参数用于创建可接受任意数量参数的函数。
  • Spread 语法用于将数组传递给通常需要含有许多参数的列表的函数。

它们俩的出现帮助我们轻松地在列表和参数数组之间来回转换。

“旧式”的 arguments(类数组且可迭代的对象)也依然能够帮助我们获取函数调用中的所有参数。


现代 JavaScript 教程:开源的现代 JavaScript 从入门到进阶的优质教程。React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程[3]

在线免费阅读:https://zh.javascript.info


参考资料

[1]

Math.max: https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Math/max

[2]

之前讲过的: https://zh.javascript.info/object-copy#ke-long-yu-he-bing-objectassign

[3]

React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程: https://zh-hans.reactjs.org/docs/getting-started.html#javascript-resources

最后

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 欢迎加我微信「qianyu443033099」拉你进技术群,长期交流学习...

  3. 关注公众号「前端下午茶」,持续为你推送精选好文,也可以加我为好友,随时聊骚。

点个在看支持我吧,转发就更好了

数组做参数_ES6 系列:你不知道的 Rest 参数与 Spread 语法细节相关推荐

  1. ES6 系列:你不知道的 Rest 参数与 Spread 语法细节

    Rest 参数与 Spread 语法 在 JavaScript 中,很多内建函数都支持传入任意数量的参数. 例如: Math.max(arg1, arg2, ..., argN) -- 返回入参中的最 ...

  2. ES6 进阶:你不知道的 Rest 参数与 Spread 语法细节

    Rest 参数与 Spread 语法 在 JavaScript 中,很多内建函数都支持传入任意数量的参数. 例如: Math.max(arg1, arg2, ..., argN) -- 返回入参中的最 ...

  3. myBatis的xml映射文件中传入list集合与数组做条件

    mybatis的xml映射文件中传入list集合与数组做条件 1.传list集合参数 1.1sql映射: <select id="queryDeptListByBankLevelAnd ...

  4. C语言-二维数组做函数的参数

    文章目录 1 引例 2 观点1 这种使用方法是错误的 3 观点2 根本不需要这么做 4 二维数组做函数参数的方法 4.1 方法1 4.2 方法2 4.3 方法3 5 与Java的不同 1 引例 下面的 ...

  5. C语言多维数组做函数参数技术推演

    多维数组做函数参数技术 C语言中只会以机械式的值拷贝的方式传递参数(实参把值传给形参) 二维数组参数同样存在退化的问题 等价关系 C语言中只会以机械式的值拷贝的方式传递参数(实参把值传给形参) int ...

  6. C语言多维数组做函数参数退化原因大剖析

    多维数组做函数参数退化原因 多维数组做函数参数退化原因大剖析 多维数组做函数参数退化原因大剖析 //证明一下多维数组的线性存储 //线性打印 void printfArray411(int *arra ...

  7. C语言中数组做函数参数的问题

    数组做函数参数,会退化成为一个指针变量.因此在进行数组参数传递的同时,需要传递一个数组长度的参数变量. 数组长度可以通过sizeof(arr)/siezof(arr[0])来得到.关于这个sizeof ...

  8. java 11:数组作为函数参数,数组做为函数返回值

    1 数组作为参数 我们可以将数组作为参数,传入到函数中,其实就像我们main函数中 public void main(String [] args){};就是用数组作为函数参数: 又如, [java] ...

  9. vector做函数参数和普通数组做函数参数有什么区别

    结论: 1,vector做函数参数,当vector参数做输出(vector数组需要在函数中被改变)的时候,需要传入vector的引用. 2,vector做函数参数,当vector参数做输入参数(只使用 ...

最新文章

  1. ArcGIS for Desktop入门教程_第二章_Desktop简介 - ArcGIS知乎-新一代ArcGIS问答社区
  2. 第一天开通博客,记录自己在编程道路上的点点滴滴
  3. 法国呼叫服务公司Aircall获得800万美元融资
  4. IE6-IE9兼容性问题列表及解决办法_补遗漏之一:button的type默认值改变为submit了。...
  5. PostgreSQL与MySQL比较
  6. Java:多线程之线程池
  7. php redis ip查找,php+redis实现ip白名单并提供可配置ip页面
  8. 【深度学习】如何配置一台深度学习工作站?
  9. 初识Docker-什么是docker
  10. 谁动了我的选择器?深入理解CSS选择器优先级
  11. 【SpringBoot基础知识】如何在springboot中使用多线程
  12. HDU 2068 Choose the best route
  13. 浅评-我所用的输入法
  14. 【C++】常用查找算法
  15. C++虚函数(多态性)
  16. 如何写一份好的软件开发的需求分析
  17. mongodb-b站黑马程序员
  18. PAT --- 1055.集体照 (25 分)
  19. Reaver无线破解工具——穷举PIN码破解简析
  20. 目录操作 递归打印目录 DIR drent

热门文章

  1. 基于卷积神经网络的垃圾图像分类算法
  2. Batch Normalization的诅咒
  3. 计算机视觉还能撑多久?
  4. 在计算机视觉方向如何快速提升自己?
  5. 10张 GIF 动图让你弄懂递归等概念
  6. 计算机视觉的优点和局限性
  7. MySQL删除表及删除表数据操作
  8. 潮州市云计算数据中心挂牌 粤东地区规模最大
  9. Oracle事务处理—隔离级别
  10. 51js 的json编辑器