Andrew Hill

@andrewjrhill

Getting to Grips with ES6: Arrow Functions

Part II: Understanding arrow functions

Click here to view Part III: Understanding standard, embedded, multiline and tagged template literals.

Preface

This article is designed as a “get started” read for beginner to intermediate developers who wish to gain a better understanding of ES6 arrow functions.

As part of my “Getting to Grips with ES6” series, I aim to create a reference containing straight forward explanations ordered in short sections to help us understand and apply these concepts directly to our current development processes.

Written by Andrew Hill
You can find me on LinkedIn, Twitter, Instagram, and GitHub.

The Arrow Function

Arrow functions (often referred to as fat arrow functions or lambda functions) are a concise way of writing functions that do not rebind context (this) within other functions. Their short syntax is further enhanced by their ability to return values implicitly in one line, single return functions.

Arrow functions can never be named and as such, are always considered anonymous functions.

Traditional Function Definitions

Until now, JavaScript has made use of the following syntax for anonymous functions:

var double = function(a) {
return a * 2;
}
double(100);
// Output: 200

There are a wide variety of syntaxes available to arrow functions; a full list of which is available on EcmaScript.org. The examples in this article will make use of the most common ones in the interest of brevity.

Arrow Function Definitions

With the introduction of arrow functions, we can rewrite the given example as follows:

const double = (a) => {
return a * 2;
}
double(100);
// Output: 200

Parameters and Parenthesis

Parenthesis are required around arrow functions with multiple parameters:

const add = (a, b) => {
return a + b;
}
add(100, 200);
// Output: 300

Parenthesis are also required in arrow functions that do not contain any parameters at all:

const fullName = () => {
return 'Andrew Hill';
}
name();
// Output: Andrew Hill

Functions that contain a single parameter allow us to omit the parenthesis from the function definition all together:

const double = a => {
return a * 2;
}
double(100);
// Output: 200

Returning Implicitly

Our example is a simple one line function that returns a value immediately. In a use case such as this, we are allowed to omit the functions curly braces in order to implicitly return its result:

const double5 = a => a * 2;
double5(100);
// Output: 200

Returning Object Literals Implicitly

Traditionally, if we wanted to return an object literal we would write our function as follows:

var animal = function(type, name) {
return {
type: type,
name: name,
}
}
animal('dog', 'Damon');
// Output: Object {type: "dog", name: "Damon"}

Switching to an arrow function and attempting to implicitly return the object will result in errors as the engine is unable to tell if the curly braces belong to the object or the function block:

const animal = (type, name) => { type: type, name: name };
animal('dog', 'Damon');
// Output: [SyntaxError] Unexpected token :

To get around this we can tell the arrow function we are implicitly returning the object by wrapping the object in an additional set of parenthesis:

const animal = (type, name) => ({ type: type, name: name });
animal('dog', 'Damon');
// Output: Object {type: "dog", name: "Damon"}

As a side note, keys and values that share a name in ES6 can be written in shorthand as follows:

const animal2 = (type, name) => ({ type, name });
animal2('dog', 'Damon');
// Output: Object {type: "dog", name: "Damon"}

It’s beautiful!

Context

Let’s take a look at the way traditional functions handle context by binding an event listener to a button and logging this on click:

var button = document.getElementById('contextButton');
button.addEventListener('click', function(){
console.log(this);
});
// Output on click: <button id="contextButton">...</button> 

In this example, the traditional function bound the value of this to the element that the event listener is assigned to.

Consider the following example using an arrow function instead:

const button = document.getElementById('contextButton');
button.addEventListener('click', () => {
console.log(this);
});
// Output on click: Window { ... }

Here we illustrate how arrow functions make use of lexical binding to inherit the value of this from the parent context. Since this code exists in the global scope, its parent (and the result of this) is the Window {} object.

Nuances and Final Thoughts

One final nuance to consider is that the arguments object is not available to arrow functions.

In the following example, we make use of a traditional function to return an array of “argument values” using the arguments object:

var test = function(a, b) {
return arguments;
}
test(100, 200);
// Output: [100, 200]

Attempting the same thing using an arrow function results in errors:

const test = (a, b) => arguments;
test(100, 200);
// Output: [Uncaught ReferenceError] arguments is not defined

Finally, it is important to realise that even though arrow functions are beautiful, short and concise, they are not here to replace traditional functions. Their use of lexical binding means there are definite use cases where we should and should not be using arrow functions.

Thanks for reading. If you liked this article please consider supporting future installations of this series by recommending it. 👏

Click here to view Part III: Understanding standard, embedded, multiline and tagged template literals.

More by Andrew Hill

Topics of interest

More Related Stories