Spread/Rest

ES6 introduces a new ... operator that’s typically referred to as the spread or rest operator, depending on where/how it’s used. Let’s take a look:

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

When ... is used in front of an array (actually, any iterable, which we cover in Chapter 3), it acts to “spread” it out into its individual values.

You’ll typically see that usage as is shown in that previous snippet, when spreading out an array as a set of arguments to a function call. In this usage, ... acts to give us a simpler syntactic replacement for the apply(..) method, which we would typically have used pre-ES6 as:

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

But ... can be used to spread out/expand a value in other contexts as well, such as inside another array declaration:

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

In this usage, ... is basically replacing concat(..), as it behaves like [1].concat( a, [5] ) here.

The other common usage of ... can be seen as essentially the opposite; instead of spreading a value out, the ... gathers a set of values together into an array. Consider:

  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]

The ...z in this snippet is essentially saying: “gather the rest of the arguments (if any) into an array called z.” Because x was assigned 1, and y was assigned 2, the rest of the arguments 3, 4, and 5 were gathered into z.

Of course, if you don’t have any named parameters, the ... gathers all arguments:

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

Note: The ...args in the foo(..) function declaration is usually called “rest parameters,” because you’re collecting the rest of the parameters. I prefer “gather,” because it’s more descriptive of what it does rather than what it contains.

The best part about this usage is that it provides a very solid alternative to using the long-since-deprecated arguments array — actually, it’s not really an array, but an array-like object. Because args (or whatever you call it — a lot of people prefer r or rest) is a real array, we can get rid of lots of silly pre-ES6 tricks we jumped through to make arguments into something we can treat as an array.

Consider:

  1. // doing things the new ES6 way
  2. function foo(...args) {
  3. // `args` is already a real array
  4. // discard first element in `args`
  5. args.shift();
  6. // pass along all of `args` as arguments
  7. // to `console.log(..)`
  8. console.log( ...args );
  9. }
  10. // doing things the old-school pre-ES6 way
  11. function bar() {
  12. // turn `arguments` into a real array
  13. var args = Array.prototype.slice.call( arguments );
  14. // add some elements on the end
  15. args.push( 4, 5 );
  16. // filter out odd numbers
  17. args = args.filter( function(v){
  18. return v % 2 == 0;
  19. } );
  20. // pass along all of `args` as arguments
  21. // to `foo(..)`
  22. foo.apply( null, args );
  23. }
  24. bar( 0, 1, 2, 3 ); // 2 4

The ...args in the foo(..) function declaration gathers arguments, and the ...args in the console.log(..) call spreads them out. That’s a good illustration of the symmetric but opposite uses of the ... operator.

Besides the ... usage in a function declaration, there’s another case where ... is used for gathering values, and we’ll look at it in the “Too Many, Too Few, Just Enough” section later in this chapter.