245 lines
6.9 KiB
Markdown
245 lines
6.9 KiB
Markdown
# Rest parameters and spread operator
|
|
|
|
Many JavaScript built-in functions support an 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 learn how to do the same. And, more importantly, how to feel comfortable working with such functions and arrays.
|
|
|
|
## Rest parameters `...`
|
|
|
|
A function can be called with any number of arguments, no matter how it is defined.
|
|
|
|
Like here:
|
|
```js run
|
|
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`:
|
|
|
|
```js run
|
|
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 the first parameters as variables, and gather only the rest.
|
|
|
|
Here the first two arguments go into variables and the rest go into `titles` array:
|
|
|
|
```js run
|
|
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");
|
|
```
|
|
|
|
````warn header="The rest parameters must be at the end"
|
|
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 last.
|
|
````
|
|
|
|
## The "arguments" variable
|
|
|
|
There is also a special array-like object named `arguments` that contains all arguments by their index.
|
|
|
|
For instance:
|
|
|
|
```js run
|
|
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 using `arguments` was the only way to get all arguments of the function, no matter their total number.
|
|
|
|
And it still works, we can use it today.
|
|
|
|
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 call `arguments.map(...)` for example.
|
|
|
|
Also, it always contains all arguments. 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:
|
|
|
|
```js run
|
|
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 a list:
|
|
|
|
```js run
|
|
alert( Math.max(3, 5, 1) ); // 5
|
|
```
|
|
|
|
Now let's say we have an array `[3, 5, 1]`. How do we call `Math.max` with it?
|
|
|
|
Passing it "as is" 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(arr[0], arr[1], arr[2])`, because we may be unsure how many there are. As our script executes, there could be a lot, or there could be none. And that would get 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
|
|
```
|
|
|
|
We can 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, the 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 the 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` tends to be 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 of arguments into an array.
|
|
- When `...` occurs in a function call or alike, it's called a "spread operator" and expands an array into a 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.
|