All Points Bulletin Board: What Are Arrow Functions?
Since beginning my journey to become a web developer I’ve kept a list — it exists on a small corkboard near my desk, connected with jewel-toned pushpins. This list is a collection of every question that has come to me in a moment when I couldn’t stop and research. I call it my “All Points Bulletin Board,” and the questions look like this:
- What is the visual formatting model?
- What do the properties
absolute
andrelative
mean? How do they work together? - What is lexical scope?
- What is 7–1 CSS structure?
- What are arrow functions? How do they work?
There are many, many questions on that list now. Some have answered themselves as I learned more and worked on more projects. Others I took the time to answer through reading documentation, google-fu and books, and many more are still unanswered.
The Notion docs have piled up, and I have decided to start sharing these questions and my answers, especially if the search to answer them is particularly illuminating.
I’ll be starting with the question that was the hardest for me to wrap my head around when I first started learning JavaScript: What are arrow functions?
Question: What are Arrow Functions?
Short Answer: Basically, shortened function expressions.
Arrow functions were introduced before I became acquainted with JavaScript. I wasn’t sure when I was seeing them, where to use them, and why I would even want to. In the beginning I didn’t even recognize them as functions — they don’t have the trademark keywords we usually see, and some don’t even require parenthesis or brackets.
const multiply = (a, b) => a * b;
Arrow functions don’t include the keyword function
and if the code block is only one statement long, like the example above, the return
keyword can be omitted. Furthermore, if only one parameter is passed in, the parentheses wrapping it can be left out.
const double = n => n * 2;
But that’s not the whole story. Function expressions and arrow functions (also known as “fat arrows”) can behave very differently, looking under the hood will explain why.
Long Answer:
To really break down what an arrow function is, I started by exploring the other ways functions are declared in JavaScript. Namely, function declaration and function expression.
A function created using function declaration is also known as a named function. These functions are hoisted to the top of their scope, and are called by their name. Named Functions effectively store functions until we need them.
// Function Declaration / Function Statement / Named Functionfunction multiplyOrDivide(a, b){
if (a % 2 === 0){
return a * b
} else {
return a / b
}
};
On the other hand, a function created using function expression is known as an anonymous function. Anonymous functions are often stored in a variable, which is how we’ll eventually call them.
You’ll see functions created this way used as IIFEs — Immediately Invoked Function Expressions. That’s a separate blog post though.
// Function Expression / Anonymous Function const multiply = function(a, b){return a * b}; console.log(multiply(5, 10));
// -> 50
The main difference between these two is the function name and **hoisting. Function declarations are hoisted, function expressions are not.
**Note: In short, hoisting refers to the JavaScript compilers way of reading code — variable declarations are ‘lifted’ to the top of their scope and they can be called before they appear in the code. The compiler does this to preemptively create space in memory, but it can create bugs if you don’t know what’s going on. (Read more about hoisting Here)
Arrow Functions: Use Cases and Gotchas
Arrow functions were introduced in ES6, in large part to reduce confusion surround the this
keyword. When writing code using Promises and Callbacks, for example, the resulting code can be a maze of return and function keywords, and this
can be hard to keep track of.
Use Case: Using an arrow function provides lexical this
In ES5 the complexities related to this
were worked around by creating closures, or using the (performatively slow) .bind()
method. Arrow functions are a salve for this— they retain the scope of the caller inside the function. Let’s look at an example.
// Function Expression / Anonymous Function API.prototype.get = function(resource) { var self = this; // closure created to bind this return new Promise(function(resolve, reject) {
http.get(self.uri + resource, function(data) {
resolve(data);
});
});
};
This example was pulled from an article by Jack Pennell, where he cites Jack Franklin as the provider. You can read the whole article here.
It’s a good example to visualize what we’re talking about. Going into the function we immediately have to bind this
and then site it in the get
method. Using an arrow function we don’t have to do this extra step.
// Arrow Function / Fat Arrow FunctionAPI.prototype.get = function(resource) {
return new Promise((resolve, reject) => {
http.get(this.uri + resource, function(data) {
resolve(data);
});
});
};
In the arrow function (that retains the scope of its caller) this
is already bound. In fact we cannot change its value. Methods like call
, apply
, and bind
will not work.
Unlike other function types, arrow functions have no prototype property — they’re more akin to a method (or an internal function) in this way. If we needed the uri in this example to be dynamic, we would not be able to use an arrow function.
Use Case: Easier to read array manipulations
ES6 came with more than just array functions (although arrow functions have been the most used addition), array methods like the very popular map
function were also introduced. Using arrow functions in methods to manipulate and read arrays can arguably make them easier to read.
// Function Expression / Anonymous Function const groceryList = [
{name: 'bananas', type: 'fruit'},
{name: 'broccoli', type: 'vegetable'},
{name: 'chicken', type: 'poultry'},
{name: 'apples', type: 'fruit'}
]; const types = groceryList.map(function(item) {
return item.type;
});
Now let’s use an arrow function:
// Arrow Function const groceryList = [
{name: 'bananas', type: 'fruit'},
{name: 'broccoli', type: 'vegetable'},
{name: 'chicken', type: 'poultry'},
{name: 'apples', type: 'fruit'}
];const types = groceryList.map(item => {
return item.type;
});
We can simplify the function even further:
// Arrow Functionconst groceryList = [
{name: 'bananas', type: 'fruit'},
{name: 'broccoli', type: 'vegetable'},
{name: 'chicken', type: 'poultry'},
{name: 'apples', type: 'fruit'}
];const types = groceryList.map(item => item.type);
Arrow functions have been widely adopted by the developer community, and for good reason. They can help us write easier to read and maintain code — but they aren’t without pitfalls, and they are not a 1:1 substitution for the function declarations and expressions we’re used to.
Arrow functions don’t have access to the arguments
object, they have no prototype
property, or other internal methods. Their scope is always bound to its caller, and cannot be changed.
You can use arrow functions when writing code that relies heavily on promises and callbacks, like in a NodeJs environment. Or when manipulating large arrays, a common task in React.
To get a better idea of when and where to use arrow functions, I recommend reading this StackOverflow thread. There many developers give insight into how their teams use (and don’t use) arrow functions.
Resources: