6 KiB
Function arguments: rest and spread
In Javascript, there is an internal type named List.
When a function is called, it is said to have "a list of parameters". That's not a figure of speech. The specification officially says that in the call f(a, b, c)
we have the function f
the list of (a, b, c)
.
[cut]
Both List and Array represent ordered collections of values.
The difference is:
Array
is the open data type that we can use, it provides special methods likepush/pop
.List
is the internal type, to represend lists of arguments and such, it has no special methods.
There are two use cases when we want to convert between them:
- To work with an arbitrary number of function arguments, it's useful to have them in the form of array.
- We have an array of values and would like to call a function that requires them to be listed.
Both variants are possible.
Rest parameters ...
The rest parameters are denoted with three dots ...
. They literally mean: "gather the list into an array".
For instance, here we 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
That also works partially.
For instance, here we put few first arguments into variables and gather only the rest of them:
function showName(firstName, lastName, ...rest) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// the rest = ["Consul", "of the Roman Republic"]
alert( rest[0] ); // Consul
alert( rest[1] ); // of the Roman Republic
alert( rest.length ); // 2
}
showName("Julius", "Caesar", "Consul", "of the Roman Republic");
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
But there is also a special array-like object named arguments
that contains all arguments by their index.
For instance:
function showName() {
alert( arguments[0] );
alert( arguments[1] );
alert( arguments.length );
// iterable too
// for(let arg of arguments) alert(arg);
}
// shows: Julius, Caesar, 2
showName("Julius", "Caesar");
// shows: Ilya, undefined, 1
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.
But the downside is that though arguments
is both array-like and iterable, it's not an array. It does not support array methods. Also, it always has everything in it, we can't get first parameters in the variable and keep only the rest in arguments.
So when we need these features, then rest parameters are preferred.
Spread operator [#spread-operator]
We've just seen how to get an array from the list of parameters.
Now let's do the reverse.
For instance, there's a built-in function Math.max that returns the greatest number from the list:
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:
let arr = [3, 5, 1];
*!*
alert( Math.max(arr) ); // NaN
*/!*
Most of the time we also can't manually list items in the code Math.max(arg[0], arg[1], arg[2])
, because their exact number is not known. As our script executes, there might be many, or there might be none.
Spread operator to the rescue. It looks similar to rest parameters, also using ...
, but does quite the opposite.
When ...iter
is used in the function call, it "expands" an iterable object iter
into the list.
For Math.max
:
let arr = [3, 5, 1];
alert( Math.max(...arr) ); // 5 (spread turns array into a list of arguments)
Unlike rest parameters, spread operators can appear as many times as needed within a single call.
Here's an example:
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
*!*
alert( Math.max(0, ...arr, 2, ...arr2) ); // 15
*/!*
When you see `"..."`, there's an easy way to differ spread operator from rest parameters:
- Rest parameters, if it's in the function definition (gathers into array).
- Spread operator, if in the function call (expands an array).
Also we can use spread operator to merge arrays:
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:
let str = "Hello";
alert( [...str] ); // H,e,l,l,o
The spread operator ...str
uses the same iterator mechanism as for..of
to iterate and gather elements. For a string, for..of
returns characters, so ...str
becomes "h","e","l","l","o"
. The list of characters is passed to array initializer []
.
Here we could also use Array.from
that converts an iterable (a string) into an array:
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]
.
Summary
- 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, it's called a "spread operator" and expands an array into the list.
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 the arguments
array-like iterable object.