This is the fourth post in the series of posts titled “The Anatomy of a JavaScript function”.

Arrow painted on a wall 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.