Arrow Functions 101
Arrow Functions
(See Chapter 3 of You Don’t Know JavaScript: Scopes and Closures by Kyle Simpson Arrow Functions)
tl;dr
tl;dr: Arrow Functions are a bit of syntactic sugar on JavaScript functions
The Arrow Function syntax lets us write lengthy boilerplate loops in a concise way. Once you know the idiom, you can replace your lengthy for-loops. For example, you can shorten this…
let fruits = ['apples', 'bananas', 'cherries', 'dates', 'elderberry'];
let capitalizedFruits = [];
for(let i=0; i<fruits.length; i++) {
let capitalized = fruits[i].toUpperCase();
capitalizedFruits.push(capitalized);
}
…to this…
let fruits = ['apples', 'bananas', 'cherries', 'dates', 'elderberry'];
let capitalizedFruits = fruits.map( f => f.toUpperCase() );
All the details
Declaring functions
To declare a function, we can use the function keyword like this:
listing 1:
function boldify(word) {
let boldWord = `<b> ${word} </b>`;
return boldWord;
}
We can do this with arrow function syntax like this:
listing 2:
const boldify = (word) => {
let boldWord = `<b> ${word} </b>`;
return boldWord;
}
This looks a bit confusing if you haven’t seen that you can assign a function to a variable! In listing 1, we are declaring a function called boldify
. On the other hand, in listing 2, we first declare a variable called boldify
, then assign an (anonymous) function to it.
Function can be assigned to variables
A very special trait of JavaScript is the ability to assign a function to a variable.
listing 3:
function boldify(word) {
return `<b> ${word} </b>`;
}
function italify(word) {
return `<i> ${word} </i>`;
}
let formatify = boldify;
console.log(formatify("I am bold!"));
formatify = italify;
console.log(formatify("I am italic!"));
In listing 3, we invoke a function stored in the formatify
variable. It can be either italify or boldify. If we wanted to declare an underlinify
or a strikeoutify
function, we could use those as well. We could assign them to formatify
to indicate the kind of format we want to use. (Note: all we are doing here is changing the “name” of the function. But this concept is really important and very special!)
As an example, let’s re-write listing 3 with arrow functions. This doesn’t save much space, but it shows that arrow functions are just functions like any other.
let boldify = (word) => {
return `<b> ${word} </b>`;
}
let italify = (word) => {
return `<i> ${word} </i>`;
}
let formatify = boldify;
console.log(formatify("I am bold!"));
formatify = italify;
console.log(formatify("I am italic!"));
Short hand arrow functions
Where arrow functions come in handy is when we use them with short hand. Let’s look at an event listener.
function handleClick() {
alert("I was clicked!");
}
document.getElementById("button-1").addEventListener("click", handleClick);
This could be re-written with an arrow function in a few different ways. Let’s lets start with this…
const handleClick = () => {
alert("I was clicked");
}
const button = document.getElementById("button-1");
button.addEventListener("click", handleClick);
If there is only one line in the function, we can shorten this by removing the curly brackets and putting the handleClick on one line. Don’t let all the equal signs confuse you! The first = is an assignment operator. The second = is part of the arrow =>.
const button = document.getElementById("button-1");
const handleClick = () => alert("I was clicked");
button.addEventListener("click", handleClick);
We can even forgo setting the variable and put it all in one line.
button.addEventListener("click", () => alert("I was clicked") );
This “one line” idiom is what arrow functions were made for!
Using arrow functions with array functions
The most beautiful usage of arrow functions is with “functional programming” and array methods are a big part of functional programming.
Let’s say you want to convert a list in JavaScript to a list in HTML. You could do it like this…
let fruits = ['apples', 'bananas', 'cherries', 'dates', 'elderberry'];
let fruitElements = [];
// 1. convert fruit names to html
for(let i=0; i<fruits.length; i++) {
let element = `<li> ${fruits[i]} </li>`;
fruitElements.push(element);
}
// 2. join the html into a single string
let fruitsHTML = '';
for(let i=0; i<fruitElements.length; i++) {
fruitsHTML += fruitElements[i] + "\n";
}
// 3. insert the produced html into the DOM
document.querySelector("ul").innerHTML = fruitsHTML;
But we can shorten this with the map method. According to the specs, the map method “creates a new array populated with the results of calling a provided function on every element in the calling array.” This is exactly what the above for loop is doing. But it can be done in idiomatic functional JavaScript like this:
let fruits = ['apples', 'bananas', 'cherries', 'dates', 'elderberry'];
// 1. convert fruit names to html
let fruitList = fruits.map( fruit => `<li> ${fruit} </li>` );
// 2. join the html into a single string
let fruitHTML = fruitList.join("\n");
// 3. insert the produced html into the DOM
document.querySelector("ul").innerHTML = fruitHTML;
The join
method “returns a new string by concatenating all of the elements in this array, separated by commas or a specified separator string.”
This is what it would look like in one line in a fluent idiomatic style…
let fruits = ['apples', 'bananas', 'cherries', 'dates', 'elderberry'];
// do steps 1, 2 and 3 all in one line!
document.querySelector("ul").innerHTML = fruits.map( fruit => `<li> ${fruit} </li>`).join("\n");
Once you see the idiom, you’ll be able to tell at a glance what is meant by the above code. You don’t need wade through all the for
statements.
Sometimes we use whitespace to make it even easier to see. (The dot at the start of the first .map
connects back to the fruits
variable on the line above it.)
let fruits = ['apples', 'bananas', 'cherries', 'dates', 'elderberry'];
document.querySelector("ul").innerHTML = fruits
.map( fruit => `<li> ${fruit} </li>`) // 1. convert fruit names to html
.join("\n"); // 2. join the html into a single string
// 3. insert the produced html into the DOM
It’s common keep going with more loops…
document.querySelector("ul").innerHTML = fruits
.map( fruit => fruit.toUpperCase() ) // make uppercase
.map( upperfruit => `<b> ${upperfruit} </b>`) // add bold
.map( boldFruit => `<li> ${boldFruit} </li>`) // add li
.join("\n"); // finally, join
Writing it this way makes use of the syntactic sugar that es6 arrow functions provide and make it clear what is going on at a glance to anyone “in the know” about the idioms.
An overview of all the flavors of function syntax
Let’s summarize all the ways we can create functions. It’s up to you how to use them to create easily readable code. Arrow functions can make code harder to read or they can simplify long boilerplate code into concise gems. With practice you’ll be able to see the difference!
We can make a function with a function keyword…
function boldify(word) {
return `<b> ${word} </b>`;
}
We can make a function with arrow syntax…
const boldify = (word) => {
return `<b> ${word} </b>`;
}
We can put it all on one line.
const boldify = (word) => { return `<b> ${word} </b>`; }
If it is just one line, we can omit the curly brackets and the return statement.
const boldify = (word) => `<b> ${word} </b>`;
If there is just one argument, we can omit the parenthesis around the argument.
const boldify = word => `<b> ${word} </b>`;