扩散/剩余
ES6引入了一个新的...
操作符,根据你在何处以及如何使用它,它一般被称作 扩散(spread) 或 剩余(rest) 操作符。让我们看一看:
function foo(x,y,z) {
console.log( x, y, z );
}
foo( ...[1,2,3] ); // 1 2 3
当...
在一个数组(实际上,是我们将在第三章中讲解的任何的 可迭代 对象)前面被使用时,它就将数组“扩散”为它的个别的值。
通常你将会在前面所展示的那样的代码段中看到这种用法,它将一个数组扩散为函数调用的一组参数。在这种用法中,...
扮演了apply(..)
方法的简约语法替代品,在前ES6中我们经常这样使用apply(..)
:
foo.apply( null, [1,2,3] ); // 1 2 3
但...
也可以在其他上下文环境中被用于扩散/展开一个值,比如在另一个数组声明内部:
var a = [2,3,4];
var b = [ 1, ...a, 5 ];
console.log( b ); // [1,2,3,4,5]
在这种用法中,...
取代了concat(..)
,它在这里的行为就像[1].concat( a, [5] )
。
另一种...
的用法常见于一种实质上相反的操作;与将值散开不同,...
将一组值 收集 到一个数组中。
function foo(x, y, ...z) {
console.log( x, y, z );
}
foo( 1, 2, 3, 4, 5 ); // 1 2 [3,4,5]
这个代码段中的...z
实质上是在说:“将 剩余的 参数值(如果有的话)收集到一个称为z
的数组中。” 因为x
被赋值为1
,而y
被赋值为2
,所以剩余的参数值3
,4
,和5
被收集进了z
。
当然,如果你没有任何命名参数,...
会收集所有的参数值:
function foo(...args) {
console.log( args );
}
foo( 1, 2, 3, 4, 5); // [1,2,3,4,5]
注意: 在foo(..)
函数声明中的...args
经常因为你向其中收集参数的剩余部分而被称为“剩余参数”。我喜欢使用“收集”这个词,因为它描述了它做什么而不是它包含什么。
这种用法最棒的地方是,它为被废弃了很久的arguments
数组 —— 实际上它不是一个真正的数组,而是一个类数组对象 —— 提供了一种非常稳健的替代方案。因为args
(无论你叫它什么 —— 许多人喜欢叫它r
或者rest
)是一个真正的数组,我们可以摆脱许多愚蠢的前ES6技巧,我们曾经通过这些技巧尽全力去使arguments
变成我们可以视之为数组的东西。
考虑如下代码:
// 使用新的ES6方式
function foo(...args) {
// `args`已经是一个真正的数组了
// 丢弃`args`中的第一个元素
args.shift();
// 将`args`的所有内容作为参数值传给`console.log(..)`
console.log( ...args );
}
// 使用老旧的前ES6方式
function bar() {
// 将`arguments`转换为一个真正的数组
var args = Array.prototype.slice.call( arguments );
// 在末尾添加一些元素
args.push( 4, 5 );
// 过滤掉所有奇数
args = args.filter( function(v){
return v % 2 == 0;
} );
// 将`args`的所有内容作为参数值传给`foo(..)`
foo.apply( null, args );
}
bar( 0, 1, 2, 3 ); // 2 4
在函数foo(..)
声明中的...args
收集参数值,而在console.log(..)
调用中的...args
将它们扩散开。这个例子很好地展示了...
操作符平行但相反的用途。
除了在函数声明中...
的用法以外,还有另一种...
被用于收集值的情况,我们将在本章稍后的“太多,太少,正合适”一节中检视它。