The Anatomy of a JavaScript function, part 4
Function arguments and the ES6 spread operator
By: Ajdin Imsirovic 20 October 2019
This is the fourth post in the series of posts titled “The Anatomy of a JavaScript function”.
Photo by Nick Fewings on Unsplash.com
If you are brand new to functions in JavaScript, I warmly recommend starting with part 1 first.
Here’s an overview of what we learned in the first three parts:
Javascript functions are like machines that we can build and run. We can define their inputs and outputs.
In the first article in this series, we built an example “machine”. In the second article, we built another, similar one - and then we saw how to generalize them.
We started with ES5 syntax - the “traditional” JavaScript, then in the third article, we compared the “old” ES5 syntax, with the more modern ES6 syntax which includes arrow functions and some other improved ways of writing functions.
In this article, we’ll delve even deeper into ES6 and learn about some practical uses of the ES6 spread operator (...
). But first, we’ll need to understand the underlying problem that it solves.
How many arguments does a function expect?
For some functions, like the concatArgs
we defined in part 2 of this article series, we know that the function expects two arguments, a
and b
, and that it will concatenate these 2 arguments.
But sometimes, we cannot be sure how many arguments a function will receive. For example, let’s say we have an addAll
function, which will add up all the arguments we give it. How could we do that?
The arguments variable
Built-in to all functions is the arguments
variable. It’s a collection: an array-like object, holding all the arguments that were passed to the function when the function was ran.
Here’s an easy way to see what values the arguments
variable holds:
console.log(arguments);
That’s it! Really simple stuff!
Of course, the above line needs to be added to an actual function definition, so let’s use it in our old pal concatArgs
:
function concatArgs(a, b) {
return a + b
}
But… where do we put it? Easy, we’ll just console.log
it before the return:
function concatArgs(a, b) {
console.log(arguments[0]);
return a + b
}
Let’s say we run it with blue cap
and green bottle
:
concatArgs("blue cap ", "green bottle");
We’ll get back the following:
blue cap
"blue cap green bottle"
We can make it a bit more self-explanatory with a sentence in our console.log
:
function concatArgs(a, b) {
console.log(`
The first argument that was passed is: ${arguments[0]}
The second argument that was passed is: ${arguments[1]}
`);
}
Let’s run concatArgs
again:
concatArgs("cork ", "wine bottle");
The, output, expectedly, now looks like this:
The first argument that was passed is: cork
The second argument that was passed is: wine bottle
Can we use the arguments
variable with arrow functions?
Let’s combine what we’ve learned in the articles so far in the series and write an improved concatArgs
:
let concatArgs2 = (a, b) => {
console.log(`
The first argument that was passed is: ${arguments[0]}
The second argument that was passed is: ${arguments[1]}
`);
return a + b
}
Now we can run it too:
concatArgs2("cork ", "wine bottle")
Here’s the output:
Uncaught ReferenceError: arguments is not defined
at concatArgs2 (<anonymous>:3:50)
at <anonymous>:1:1
Why can’t we use arguments
in arrow functions?
Why the arguments is not defined
error, when we clearly could use it before?
Here’s the answer.
Arrow functions simply don’t have the arguments
variable like other functions do. However, sometimes we still need to be able to work with a collection of arguments that were passed to a function when it was called.
Enter the ...
, aka the spread operator.
How does the spread operator work in arrow functions?
The spread operator simply gets placed before the variable that acts sort of like a replacement for the mentioned arguments
local variable. Then in the body of the function we can just do whatever we need with it. Here’s an example definition of concatArgs3
which will log out to the console each individual argument passed to the function.
let concatArgs3 = (...argsPassed) => {
argsPassed.forEach(argPassed => console.log(argPassed) )
}
Let’s now try calling concatArgs3
with three arguments.
concatArgs3("cork ", "wine bottle ", "label ");
Here’s the output:
cork
wine bottle
label
To concatenate all the arguments that get passed, we can now simply do this:
let allArgsConcatenated;
let concatArgs4 = (...argsPassed) => {
allArgsConcatenated = argsPassed.reduce(
(accumulator, item) => accumulator + item
);
console.log(allArgsConcatenated);
}
concatArgs4("cork ", "wine bottle ", "label ");
The above code will output: cork wine bottle label
.
To understand how the reduce
iterable method works, check out the in-depth article on looping over arrays.
Now we’ve got all the necessary ingredients to make any argument we pass to our functions behave any way we want.
Understanding the arguments
local variable in depth
Now we’ll go back to the arguments
local variable in non-arrow functions.
At first glance, arguments
might seem like a regular array. Let’s revisit the code:
function concatArgs(a, b) {
console.log(arguments[0]);
console.log(arguments[1]);
return a + b
}
concatArgs("blue cap ", "green bottle");
Especially looking at arguments[0]
and arguments[1]
, we might conclude that arguments
is just an array. However, rather than being an array, it’s what’s known as an “array-like” collection.
Being array-like, it allows us to get and set the values of arguments
using syntax like arguments[0]
, arguments[1]
, etc.
What the arguments
local variable doesn’t have are a number of methods that regular arrays have, such as forEach
, or splice
.
This completes part 4 of The Anatomy of a JavaScript function article series.