原文链接:http://javascript.info/rest-parameters-spread-operator,translate with ❤️ by zhangbao.

许多 JavaScript 内置的函数支持任意数量的参数调用。

例如:

  • Math.max(arg1, arg2, ..., argN):返回 N 个参数里最大的一个。

  • Object.assign(dest, src1, ..., srcN):将 src1srcN 对象里的属性(包括 StringSymbol 类型的属性)复制到 dest

  • 等等……

在这一章中,我们将学习如何做同样的事情。更重要的是,如何更舒服地使用这些函数和数组。

剩余参数 …

一个函数调用时可以使用任意数量的参数,无论函数定义的方式如何。

像这样:

  1. function sum(a, b) {
  2. return a + b;
  3. }
  4. alert( sum(1, 2, 3, 4, 5) );

调用参数时,多余的参数调用,并不会导致错误发生。结果只会头两个参数被接收。

剩余参数出现在定义函数的时候,用三个点 ... 表示,它们的字面意思是“将剩余的参数收集到一个数组中”。

例如,将所有的参数收集到 args 中:

  1. function sumAll(...args) { // args is the name for the array
  2. let sum = 0;
  3. for (let arg of args) sum += arg;
  4. return sum;
  5. }
  6. alert( sumAll(1) ); // 1
  7. alert( sumAll(1, 2) ); // 3
  8. alert( sumAll(1, 2, 3) ); // 6

我们可以选择将第一个参数作为变量,只收集其余的参数。

在这里,前两个参数进入变量,其余的则进入 titles 数组中:

  1. function showName(firstName, lastName, ...titles) {
  2. alert( firstName + ' ' + lastName ); // Julius Caesar
  3. // 剩余参数进入了 titles 数组
  4. // i.e. titles = ["Consul", "Imperator"]
  5. alert( titles[0] ); // Consul
  6. alert( titles[1] ); // Imperator
  7. alert( titles.length ); // 2
  8. }
  9. showName("Julius", "Caesar", "Consul", "Imperator");

注:剩余参数必须作为最后一个参数出现

剩余参数就是收集所有剩余的参数,所以只能放在最后一个位置。因此下面使用的剩余参数的方式是错误的:

  1. function f(arg1, ...rest, arg2) { // arg2 在 ...rest 之后?!
  2. // 出错
  3. }

再强调一边,剩余参数只能放在最后一个位置。

“arguments”变量

函数中,还存在一个特殊的类数组对象,叫 arguments,依据索引包含所有参数。

例如:

  1. function showName() {
  2. alert( arguments.length );
  3. alert( arguments[0] );
  4. alert( arguments[1] );
  5. // arguments 是可迭代的
  6. // for(let arg of arguments) alert(arg);
  7. }
  8. // 展示: 2, Julius, Caesar
  9. showName("Julius", "Caesar");
  10. // 展示: 1, Ilya, undefined (没有第二个参数)
  11. showName("Ilya");

在旧时代,剩余参数在语言中并不存在,使用 arguments 是获得函数所有参数的惟一方法,不管它们的总数是多少。

今天,它仍然有效。

但缺点是尽管参数都是类数组对象,并且是可迭代的,但它不是一个真正的数组,它不支持数组方法,所以我们不能调用诸如 arguments.map(...) 之类的方法对参数做处理。

而且,arguments 总是包含所有的参数。我们不能部分地捕获它们,就像我们用剩余参数所做的那样。

因此,当我们需要这些特性时,剩余参数是首选的。、

注:箭头函数中没有“arguments

如果我们在箭头函数里访问 arguments,他会拿外部“正常”函数里的。

这是个例子:

  1. function f() {
  2. let showArg = () => alert(arguments[0]);
  3. showArg();
  4. }
  5. f(1); // 1

正如我们所记得的,箭头函数没有它们自己的 this,现在我们知道它们也没有特殊的 arguments 对象。

扩展运算符

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

但有时我们需要做的恰恰相反。

例如,有一个内置函数 Math.max,用于从列表中返回最大的数字:

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

现在假设我们有一个数组 [3, 5, 1]。我们怎么用 Math.max 调用它呢?

把它按照原样传递是行不通的,因为 Math.max 希望接收的是一个数字参数列表,而不是一个数组:

  1. let arr = [3, 5, 1];
  2. alert( Math.max(arr) ); // NaN

当然,我们不能在代码中手动列出所有项目 Math.max(arr[0],arr[1],arr[2]),因为我们不能确定有多少个。当我们的脚本执行时,可能会有很多,又或者没有。这样代码就会变得很难看。

扩展运算符就是来解决这个问题的!它看起来类似于剩余参数,也使用 ...,但却做相反的事情。

...arr 用在函数调用时,它表示将一个可迭代对象 arr 分解成一个参数列表。

Math.max 举个例子:

  1. let arr = [1, 2, 3];
  2. alert( Math.max(...arr) ); // 5(把数组展开成一个参数列表)

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

  1. let arr1 = [1, -2, 3, 4];
  2. let arr2 = [8, 3, -8, 1];
  3. alert( Math.max(...arr1, ...arr2) ); // 8

我们甚至可以将扩展运算符与正常值相结合:

  1. let arr1 = [1, -2, 3, 4];
  2. let arr2 = [8, 3, -8, 1];
  3. alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25

此外,扩展操作符可以用于合并数组:

  1. let arr = [3, 5, 1];
  2. let arr2 = [8, 9, 15];
  3. let merged = [0, ...arr, 2, ...arr2];
  4. alert(merged); // 0,3,5,1,2,8,9,15 (0, 然后是 arr, 然后是 2, 最后是 arr2)

在上面的例子中,我们使用了一个数组来演示扩展操作符,其实任何可迭代对象都是可以的。

例如,在这里,我们使扩展操作符将字符串转换成字符数组:

  1. let str = "Hello";
  2. alert( [...str] ); // H,e,l,l,o

扩展运算符内部使用迭代器来收集元素,跟 for..of 的原理是一样的。

因此,对于一个字符串,for..of 返回字符 ...str 变成了 "H","e","l","l","o",字符列表被传递给数组初始化器 [...str]

对于这个特殊的任务,我们还可以使用 Array.from,因为它能够将一个可迭代对象(比如字符串)转换成一个数组:

  1. let str = "Hello";
  2. // Array.from 将可迭代转换为数组
  3. alert( Array.from(str) ); // H,e,l,l,o

结果与 [...str] 一样。但是 Array.from(obj)[...obj] 之间有一个细微的差别:

  • Array.from 操作符既可以作用在类数组对象上,也可以作用在可迭代对象上。

  • 扩展运算符只能作用在可迭代对象上。

因此,对于将某些东西转换成数组的任务来说,使用 Array.from 更加普遍。

总结

当我们在代码中看到 ... 时,它要么是剩余参数,要么是扩展运算符。

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

  • ... 出现在函数参数的末尾时,它就是“剩余参数”,收集其余的参数列表到一个数组中。

  • ... 出现在函数调用或者类似场景中,它就担作“扩展运算符”,将一个数组展开成一个参数列表。

使用方式:

  • 剩余参数用于创建接收任意数量参数的函数。

  • 扩展操作符用于将数组传递给通常需要大量参数列表的函数。

它们一起帮助在参数列表和数字参数之间轻松地切换。

调用函数时的所有参数列表也可以从“旧式”arguments 对象获取到:它是一个类数组、可迭代对象。

(完)