扩散/剩余

ES6引入了一个新的...操作符,根据你在何处以及如何使用它,它一般被称作 扩散(spread)剩余(rest) 操作符。让我们看一看:

  1. function foo(x,y,z) {
  2. console.log( x, y, z );
  3. }
  4. foo( ...[1,2,3] ); // 1 2 3

...在一个数组(实际上,是我们将在第三章中讲解的任何的 可迭代 对象)前面被使用时,它就将数组“扩散”为它的个别的值。

通常你将会在前面所展示的那样的代码段中看到这种用法,它将一个数组扩散为函数调用的一组参数。在这种用法中,...扮演了apply(..)方法的简约语法替代品,在前ES6中我们经常这样使用apply(..)

  1. foo.apply( null, [1,2,3] ); // 1 2 3

...也可以在其他上下文环境中被用于扩散/展开一个值,比如在另一个数组声明内部:

  1. var a = [2,3,4];
  2. var b = [ 1, ...a, 5 ];
  3. console.log( b ); // [1,2,3,4,5]

在这种用法中,...取代了concat(..),它在这里的行为就像[1].concat( a, [5] )

另一种...的用法常见于一种实质上相反的操作;与将值散开不同,...将一组值 收集 到一个数组中。

  1. function foo(x, y, ...z) {
  2. console.log( x, y, z );
  3. }
  4. foo( 1, 2, 3, 4, 5 ); // 1 2 [3,4,5]

这个代码段中的...z实质上是在说:“将 剩余的 参数值(如果有的话)收集到一个称为z的数组中。” 因为x被赋值为1,而y被赋值为2,所以剩余的参数值34,和5被收集进了z

当然,如果你没有任何命名参数,...会收集所有的参数值:

  1. function foo(...args) {
  2. console.log( args );
  3. }
  4. foo( 1, 2, 3, 4, 5); // [1,2,3,4,5]

注意:foo(..)函数声明中的...args经常因为你向其中收集参数的剩余部分而被称为“剩余参数”。我喜欢使用“收集”这个词,因为它描述了它做什么而不是它包含什么。

这种用法最棒的地方是,它为被废弃了很久的arguments数组 —— 实际上它不是一个真正的数组,而是一个类数组对象 —— 提供了一种非常稳健的替代方案。因为args(无论你叫它什么 —— 许多人喜欢叫它r或者rest)是一个真正的数组,我们可以摆脱许多愚蠢的前ES6技巧,我们曾经通过这些技巧尽全力去使arguments变成我们可以视之为数组的东西。

考虑如下代码:

  1. // 使用新的ES6方式
  2. function foo(...args) {
  3. // `args`已经是一个真正的数组了
  4. // 丢弃`args`中的第一个元素
  5. args.shift();
  6. // 将`args`的所有内容作为参数值传给`console.log(..)`
  7. console.log( ...args );
  8. }
  9. // 使用老旧的前ES6方式
  10. function bar() {
  11. // 将`arguments`转换为一个真正的数组
  12. var args = Array.prototype.slice.call( arguments );
  13. // 在末尾添加一些元素
  14. args.push( 4, 5 );
  15. // 过滤掉所有奇数
  16. args = args.filter( function(v){
  17. return v % 2 == 0;
  18. } );
  19. // 将`args`的所有内容作为参数值传给`foo(..)`
  20. foo.apply( null, args );
  21. }
  22. bar( 0, 1, 2, 3 ); // 2 4

在函数foo(..)声明中的...args收集参数值,而在console.log(..)调用中的...args将它们扩散开。这个例子很好地展示了...操作符平行但相反的用途。

除了在函数声明中...的用法以外,还有另一种...被用于收集值的情况,我们将在本章稍后的“太多,太少,正合适”一节中检视它。