Javascript's three dots ( ... ): Spread vs rest operators

Javascript's ECMA6 came out with some cool new features; ... is one of these new Javascript functionalities. It can be used in two different ways; as a spread operator OR as a rest parameter.

Rest parameter: collects all remaining elements into an array.

Spread operator: allows iterables( arrays / objects / strings ) to be expanded into single arguments/elements.

In this article we will look into these two ways of using ...; keeping it short and sweet as usual. Enjoy!

Rest parameters

From the definition we saw earlier, rest parameters collect all the remaining elements into an array. This allows us to do really neat function definitions. Let's see how we put them to use.

function add(x, y) {
  return x + y;
}

add(1, 2, 3, 4, 5) // returns 3

The above function call returns 3, this is because in Javascript it is possible to call a function with any number of arguments. However, only the fist two arguments will be counted.

With rest parameters we can gather any number of arguments into an array and do what we want with them. So we can re-write the add function like this:

function add(...args) {
  let result = 0;

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

  return result
}

add(1) // returns 1
add(1,2) // returns 3
add(1, 2, 3, 4, 5) // returns 15

Note: Rest parameters have to be at the last argument. This is because it collects all remaining/ excess arguments into an array. So having a function definition like this does not make sense and it errors out. :

function abc(a, ...b, c) {
  ...
  return;
}

We can separately define the first arguments, and the rest of the arguments in the function call (no matter how many they are) will be collected into an array by the rest parameter.

function xyz(x, y, ...z) {
  console.log(x, ' ', y); // hey hello

  console.log(z); // ["wassup", "goodmorning", "hi", "howdy"]
  console.log(z[0]); // wassup
  console.log(z.length); // 4
}

xyz("hey", "hello", "wassup", "goodmorning", "hi", "howdy")

Since the rest parameter gives us an array, we can use array methods like Array.find e.t.c.

Arguments keyword

Before rest parameters existed, to get all the arguments in a function we used arguments which is an array-likeobject.

function someFunction() {
  return arguments;
}

someFunction("joykare", 100, false);

someFunction returns the arguments and their indexes, [Arguments] { '0': 'joykare', '1': 100, '2': false }.

The downside of using the arguments keyword is that, it returns an array-like object; this means you essentially cannot perform any array-methods like; Array.filer, Array.map. Another pitfall, is that we cannot use arguments in arrow functions. This is because arrow-functions do not have their own this, and hence no arguments object either.

Spread operators

The spread operator allows us to expand elements. With rest parameters we were able to get a list of arguments into an array. spread operators however, let us unpack elements in an array to single/individual arguments.

Some scenarios where this capability is useful include:

Adding array elements to an existing array

const arr = ["Joy", "Wangari", "Warugu"];
const newArr = ["joykare", ...arr];

The value of newArr will be [ 'joykare', 'Joy', 'Wangari', 'Warugu' ]. Note: Unlike rest parameters you can use the spread operator as the first argument. So if you wanted to add an element as the last element in your array you cna do this:

const myNames = [...arr, "joykare"];

The value of names in this case will be [ 'Joy', 'Wangari', 'Warugu', 'joykare' ].

Copying arrays

We can use the spread operator to copy an array.

const arr = [1, 2, 3];
const arr2 = [...arr];

This copies arr into arr2. Now we can do things on arr2 and any changes done to arr2 will not have any effect arr.

Pass elements of an array to a function as separate arguments

If we had an array that we wanted to pass as a list of arguments in a function, we would use the spread operator. Let's reuse our add function.

function add(a, b, c) {
  return a + b + c ;
}
const args = [1, 2, 3];

add(...args);

The add function call is similar to doing this: add(1, 2, 3).

Note: We have been using arrays to demonstrate the spread operator, but any iterable also works. So, if we had a string const str = 'joykare', [...str] translates to [ 'j', 'o', 'y', 'k', 'a', 'r', 'e' ].

Conclusion

... could be used to represent either a spread operator or a rest parameter. How do we tell the difference? Well it entirely depends on how we use it. Given the context in which we use the three dots, it is easy to tell whether we are using it as a rest parameter or a spread operator.

I hope this article makes these two concepts clearer. For a very simplified explanation on the difference, I personally loved this illustration by Stephanie Nemeth. Make sure to follow her on twitter for similar code facts illustrations.