en.javascript.info/1-js/06-advanced-functions/02-rest-parameters-spread-operator/article.md
Thierry Parmentelat 27a491f265 today’s typos
2017-05-11 20:41:00 +02:00

6.9 KiB

Rest parameters and spread operator

Many JavaScript built-in functions support on arbitrary number of arguments.

For instance:

  • Math.max(arg1, arg2, ..., argN) -- returns the greatest of the arguments.
  • Object.assign(dest, src1, ..., srcN) -- copies properties from src1..N into dest.
  • ...and so on.

In this chapter we'll see how to do the same. And, more important, how to feel comfortable working with such functions and arrays.

[cut]

Rest parameters ...

A function can be called with any number of arguments, no matter how it is defined.

Like here:

function sum(a, b) {
  return a + b;
}

alert( sum(1, 2, 3, 4, 5) );

There will be no error because of "excessive" arguments. But of course in the result only the first two will be counted.

The rest parameters can be mentioned in a function definition with three dots .... They literally mean: "gather the remaining parameters into an array".

For instance, to gather all arguments into array args:

function sumAll(...args) { // args is the name for the array
  let sum = 0;

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

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6

We can choose to get first parameters as variables, and gather only the rest.

Here the first two arguments go into variables and the rest goes to titles array:

function showName(firstName, lastName, ...titles) {
  alert( firstName + ' ' + lastName ); // Julius Caesar

  // the rest go into titles array
  // i.e. titles = ["Consul", "Imperator"]
  alert( titles[0] ); // Consul
  alert( titles[1] ); // Imperator
  alert( titles.length ); // 2
}

showName("Julius", "Caesar", "Consul", "Imperator");
The rest parameters gather all remaining arguments, so the following has no sense:

```js
function f(arg1, ...rest, arg2) { // arg2 after ...rest ?!
  // error
}
```

The `...rest` must always be the last.

The "arguments" variable

There is also a special array-like object named arguments that contains all arguments by their index.

For instance:

function showName() {
  alert( arguments.length );
  alert( arguments[0] );
  alert( arguments[1] );

  // it's iterable
  // for(let arg of arguments) alert(arg);
}

// shows: 2, Julius, Caesar
showName("Julius", "Caesar");

// shows: 1, Ilya, undefined (no second argument)
showName("Ilya");

In old times, rest parameters did not exist in the language, and arguments was the only way to get all arguments of the function no matter of their total number.

And it still works, we can use it.

But the downside is that although arguments is both array-like and iterable, it's not an array. It does not support array methods, so we can't say call arguments.map(...).

Also, it always has all arguments in it, we can't capture them partially, like we did with rest parameters.

So when we need these features, then rest parameters are preferred.

````smart header="Arrow functions do not have \"arguments\"" If we access the arguments object from an arrow function, it takes them from the outer "normal" function.

Here's an example:

function f() {
  let showArg = () => alert(arguments[0]);
  showArg();
}

f(1); // 1

As we remember, arrow functions don't have their own this. Now we know they don't have the special arguments object either.


## Spread operator [#spread-operator]

We've just seen how to get an array from the list of parameters.

But sometimes we need to do exactly the reverse.

For instance, there's a built-in function [Math.max](mdn:js/Math/max) that returns the greatest number from the list:

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

Now let's say we have an array `[3, 5, 1]`. How to call `Math.max` with it?

Passing it "as it" won't work, because `Math.max` expects a list of numeric arguments, not a single array:

```js run
let arr = [3, 5, 1];

*!*
alert( Math.max(arr) ); // NaN
*/!*
```

...And surely we can't manually list items in the code `Math.max(arg[0], arg[1], arg[2])`, because we may be unsure how much are there. As our script executes, there might be many, or there might be none. Also that would be ugly.

*Spread operator* to the rescue. It looks similar to rest parameters, also using `...`, but does quite the opposite.

When `...arr` is used in the function call, it "expands" an iterable object `arr` into the list of arguments.

For `Math.max`:

```js run
let arr = [3, 5, 1];

alert( Math.max(...arr) ); // 5 (spread turns array into a list of arguments)
```

We also can pass multiple iterables this way:

```js run
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(...arr1, ...arr2) ); // 8
```

...And even combine the spread operator with normal values:


```js run
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];

alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
```

Also spread operator can be used to merge arrays:

```js run
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];

*!*
let merged = [0, ...arr, 2, ...arr2];
*/!*

alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)
```

In the examples above we used an array to demonstrate the spread operator, but any iterable will do.

For instance, here we use spread operator to turn the string into array of characters:

```js run
let str = "Hello";

alert( [...str] ); // H,e,l,l,o
```

The spread operator internally uses iterators to gather elements, the same way as `for..of` does.

So, for a string, `for..of` returns characters and `...str` becomes `"h","e","l","l","o"`. The list of characters is passed to array initializer `[...str]`.

For this particular task we could also use `Array.from`, because it converts an iterable (like a string) into an array:

```js run
let str = "Hello";

// Array.from converts an iterable into an array
alert( Array.from(str) ); // H,e,l,l,o
```

The result is the same as `[...str]`.

But there's a subtle difference between `Array.from(obj)` and `[...obj]`:

- `Array.from` operates on both array-likes and iterables.
- The spread operator operates only on iterables.

So, for the task of turning something into an array, `Array.from` appears more universal.


## Summary

When we see `"..."` in the code, it is either rest parameters or the spread operator.

There's an easy way to distinguish between them:

- When `...` is at the end of function parameters, it's "rest parameters" and gathers the rest of the list into the array.
- When `...` occurs in a function call or alike, it's called a "spread operator" and expands an array into the list.

Use patterns:

- Rest parameters are used to create functions that accept any number of arguments.
- The spread operator is used to pass an array to functions that normally require a list of many arguments.

Together they help to travel between a list and an array of parameters with ease.

All arguments of a function call are also available in "old-style" `arguments`: array-like iterable object.