Discovering Arrow Functions in Javascript

By Mike Sharp
03 May, 2021

Github icon
Back to posts

In this post, we're taking a look at arrow functions, which are a great addition to the Javascript syntax.

Once you see how arrow functions work and how useful they are, you'll want to use them everywhere!

Let's start by taking a crash course on the arrow function syntax and then we'll explore exactly where and when you should use then and we'll look at a range of programming situations where arrow functions shine.

Finally, we'll wrap up by exploring when it's best not to use arrow functions and examine any potential gotchas of using this versatile and modern syntax.

What does an arrow function look like?

So, to cut to the chase, this is how we write an arrow function in JavaScript; Here is a basic arrow function that takes some arguments and prints them to the console.

(a, b, c) => {
    console.log(a, b, c);
}

The arrow function consists of three parts:

  • The argument list
  • The fat arrow symbol =>
  • The body of the arrow function expression

In arrow functions, the function keyword that you would normally write when writing a function is replaced by the "fat arrow" symbol, with the fat arrow being made up of the = and > symbols put together instead to build the fat arrow.

The list of arguments is also at the beginning of the function, before the fat arrow instead of being after the function keyword, which as mentioned above isn't present in the arrow function syntax.

The function body can be written in several ways, so let's explore the different forms that you can use with the arrow function syntax.

Arrow functions come in many shapes and sizes

Multi-line

This is the closest to the form you would use in a regular function, but there are some important differences, let's take a look at an example.

const  reverseAndUppercase  = (str) => {
    const  reversed  =  str.split('').reverse().join('');
    const  uppercased  =  reversed.toUpperCase();
    return  uppercased;
};
reverseAndUppercase('hello world!');
output: "!DLROW OLLEH"

In the example above, the function contains multiple statements, so you need to surround those statements with braces { } and use the return keyword if you want the function to return a value, but you can omit the function keyword and use the fat arrow => instead.

The fat arrow in this situation is just shorthand for function from a syntactical point of view, but there are some functional (for want of a better word) differences that we'll cover later.

Single-line

If the function body only contains one statement, you can omit the return keyword and the braces you'd normally use in a function.

Arrow functions will return whatever value you specify to the right of the fat arrow operator => when the function is called

const  sumThree  = (a, b, c) =>  a  +  b  +  c;
sumThree(1, 2, 3);
output: 6

Single-parameter

You can omit the brackets around the argument list if you only have one argument, but you must include them if you have zero or more than one arguments.

const  square  = num =>  num  *  num;
square(4);
output: 16

As you can see in the example above, arrow functions can be very compact, but still very descriptive and easy to read.

Arrow functions as immediately invoked function expressions (IIFE)

Arrow functions can either be assigned to variables or you can define them and call them immediately without naming them, this is known as an immediately invoked function expression or IIFE

// An arrow function can be declared and defined as a constant and then 
// called later...
const  sumThree  = (a, b, c) =>  a  +  b  +  c;
sumThree(1, 2, 3);
// ...but can also be defined and invoked/called immediately
// in the same statement.
((a, b, c) =>  a  +  b  +  c)(1, 2, 3);
//   ^               ^           ^
// [arguments] [function body]  [invoke with arguments immediately]

The compactness of arrow function syntax works well when writing IIFE's, since you can dispense with syntactic bloat you would usually add to make a function more re-usable since this is not a concern when you're only going to run the IIFE once.

So now that we've had a bit of a crash course on how to spot an arrow function in the wild, let's explore the various ways we can use them and why we would want to instead of using traditional function expressions.

Benefits of using arrow functions

So why would you use an arrow function instead of writing one the old-fashioned way? Well not only will you get massive programming street cred for writing Javascript in a nice modern syntax, but you should also see improvements in readability and more granular control over the scope of variables created in your application.

let's take a look at those benefits in detail.

More concise and readable

Arrow functions are a lot more concise than writing out a regular old Javascript function, the new syntax does away with a lot of the excess syntax needed to represent a function without damaging readability.

Arguably, it makes it even more readable, and I'm personally in that camp.

Once you get used to writing arrow functions you'll never know how you lived without them!

Here's an example of a function expression written using the traditional function keyword syntax and here's the equivalent arrow function.

// The bloated old Javascript function
function sumThree(a, b, c) {
    return  a  +  b  +  c;
}

// The cool new arrow function syntax
const sumThree = ((a, b, c) =>  a  +  b  +  c);

Note how the first function took 3 statements to define the same logic that the arrow function managed to do the same with just one statement. None of the clarity was lost in the process either, the old syntax is just more verbose.

Arrow functions just made scope a lot easier to manage

Another major difference between a regular Javascript function and an arrow function is how scope gets handled when the function gets called.

Scope is important in Javascript because it determines how a variable or constant is defined at a given point in the execution of the code in your applications.

Javascript handles scope in a rather unique way, new scope is created inside of a function when it gets called, this is called function scope

if you would like a more detailed explanation of how scope works in Javascript, I talked about it in my other blog about const and let and you can find it right here.

To reference the current scope of a Javascript application, you use the this keyword, this is a kind of pointer to whatever is set as the current scope, initially, this is the Window object until you call a function or you're inside of a constructor or method inside of an ES5 class.

Behaviour of this inside regular Javascript functions

Here's an example of how not to use this in Javascript.

In this example the application defines a class that contains a method called determineYesOrNo which randomly returns 'Yes' or 'No' each time the method is called, the method stores the result in a property called yesNo, which starts with the initial value of null;

The example uses Javascript Promises, which allow you to carry out asynchronous operations (think loading data from external APIs or using libraries that rely on concurrency, but in this example, we're just setting values on an object)

This is where the issue of scope and defining what this is within a given point in the life of the application is important, as this keeps changing as nested functions get called.

// Using 'this' with vanilla functions, the default behaviour

class DeciderRegular {
    constructor() {
        this.yesNo = null;
    }
    determineYesOrNo() {
        // get Yes or No result from asynchronous function and process response 
        // using standard function syntax
        const yesNoResult = new Promise(function (resolve) {
            return resolve(Math.round(Math.random()) ? 'Yes' : 'No');
        });
        return yesNoResult.then(function (result) {
            // hitting this line of code will result in a JavaScript error 
            // because 'this'  doesn't point to the object inside of a regular 
            // function 
            // a new 'this' has been created inside of the current function, 
            // overriding the 'this' in the object because of function scope.
            this.yesNo = result;
        });
    }
}

// create instance of Decider
const myRegularDecider = new DeciderRegular();
// initial value should be null
console.log(myRegularDecider.yesNo);
// code will crash here, because 'this' doesn't point to 
// myRegularDecider.yesNo inside this function anymore
myRegularDecider.determineYesOrNo()
    .then(function () {
        // code will have crashed before this point is hit,
        // but the value would still be 'null'
        console.log(myRegularDecider.yesNo)
    })

Running this results in a Javascript error:

Output: Uncaught (in promise) TypeError: Cannot set property 'yesNo' of undefined
    at <anonymous>:17:24

So why did the code throw an error? Well, This happened when the application tried to set the property this.yesNo to the result of the 'yesNoResult' function when it called back.

The problem is that the code was expecting this.yesNo to be defined and it wasn't, because this no longer referred to the instance of the DeciderRegular class, but instead was referencing the callback function defined within the yesNoResult promise callback function, this is because functions by default create a new scope in Javascript and this is automatically bound to the new scope, making the old scope inaccessible through the this keyword.

There are ways to get around this limitation, however, let's take a look at some different approaches.

Binding this to a proxy or 'that' variable

Before arrow functions were added to Javascript, a common way of keeping scope consistent between function calls was to define a proxy variable.

You would define a variable, commonly called that and assign it to whatever version of this you wanted to capture as your scope, then through the magic of function scope, your Javascript app would be able to access that because function scope allows nested functions to reference variables declared in outer functions.

// using a 'that' proxy variable to counteract scoping issue
class DeciderRegularWithThat {
    constructor() {
        this.yesNo = null;
    }
    determineYesOrNo() {
		// Assign 'this' that refers to object state to proxy variable 'that'
        const that = this;
        const yesNoResult = new Promise(function (resolve) {
            return resolve(Math.round(Math.random()) ? 'Yes' : 'No');
        });
        return yesNoResult.then(function (result) {
			// This is now OK, because 'that' is pointing to object scope
            that.yesNo = result;
        });
    }
}

// create instance of Decider
const myRegularDeciderWithThat = new DeciderRegularWithThat();
// initial value should be null
console.log(myRegularDeciderWithThat.yesNo);
myRegularDeciderWithThat.determineYesOrNo()
    .then(function () {
        console.log(myRegularDeciderWithThat.yesNo)
    })
Output: "No"

Using proxy variables largely mitigate the problem of scoping in nested functions, but let's be honest, it is a bit of a hack, isn't it?

With that in mind, let's look at another solution that is valid, but a bit sub-optimal.

Using Function.prototype.bind()

Another approach is to use one of Javascripts' built-in function prototype methods to explicitly set the this object within a function call, the Function.prototype.bind(this) is usually the simplest choice for this purpose.

// using a Function.prototype.bind(this) to counteract scoping issue
class DeciderRegularWithBind {
    constructor() {
        this.yesNo = null;
    }
    determineYesOrNo() {
		// provide a success callback to bind 'this' to
        function resolver(resolve) {
            return resolve(Math.round(Math.random()) ? 'Yes' : 'No');
        }
		// call 'bind' on callback function to set 'this'
        const yesNoResult = new Promise(resolver.bind(this));

        function setYesNo(result) {
            this.yesNo = result;
        }
		// bind 'this' to ensure we're able to access correct scope
        return yesNoResult.then(setYesNo.bind(this));
    }
}

// create instance of Decider
const myRegularDeciderWithBind = new DeciderRegularWithBind();
// initial value should be null
console.log(myRegularDeciderWithBind.yesNo);
myRegularDeciderWithBind.determineYesOrNo()
    .then(function () {
        console.log(myRegularDeciderWithBind.yesNo)
    })
Output: "No"

Setting this explicitly using .bind(this) isn't too bad a solution, but the syntax can get a bit unruly if the code contains long chains of functions that rely on a consistent this (for example, promise chains that need to read or mutate the same object throughout the chain) or if the function calls in your chain have lots of arguments, then you need to start adding those arguments into the .bind(this, arg1, arg2, argN...) method which can become difficult to read.

Let's now look at arrow functions which have none of these shortcomings and are just plain awesome.

Arrow functions do the job better

The first thing you'll notice about the example below is how compact the code is to the other versions, we get to take advantage of the sleeker syntax of the fat arrow => and we don't have to write any additional code to set this to the desired scope. Using arrow functions for this is a win-win situation.

// using 'this' with an arrow function, correct 'this'
// should be preserved without work arounds
class DeciderArrow {
    constructor() {
        this.yesNo = null;
    }
    determineYesOrNo() {
        // get Yes or No result from asynchronous function and
        // process response using arrow functions
        const yesNoResult = new Promise((resolve) => {
            return resolve(Math.round(Math.random()) ? 'Yes' : 'No');
        });
        return yesNoResult.then((result) => {
            // update object scope using this;
            this.yesNo = result;
        });
    }
}

// create instance of Decider
const myDecider = new DeciderArrow();
// initial value should be null
console.log(myDecider.yesNo);
myDecider.determineYesOrNo()
    .then(() => {
        console.log(myDecider.yesNo)
    })
Output: "No"

Auto binding is awesome

Arrow functions automatically bind this to the this of the calling function, encapsulating object or Window object if the arrow function is called outside of any function at all.

Auto-binding of this turns out to be incredibly handy when you start chaining (calling a function and taking the return value as the input to the next function call) or nesting (calling functions inside of other functions) function calls.

When you can keep a consistent handle of this, it can make code easier to debug and break up into individual units, which makes it easier to test and re-use for other purposes.

Do I need to say anymore? This is how writing Javascript should be, simple and concise!

Arrow functions are great for writing callback functions

Often, when you make use of the built-in Array prototype functions or other utility functions from 3rd party libraries they will be written in a way that lets you plug in custom behaviour when you call them to adapt their behaviour to your particular scenario.

Examples of this are array.map and array.reduce.

When you write an array map or reduce function you can specify an anonymous callback function to affect the output.

const  arrayOfNumbers  = [1, 2, 3];
// sum all numbers in array by using an arrow function
arrayOfNumbers.reduce((accumulator, currentValue) =>  accumulator  +  currentValue);
// a variant where the value is doubled before being summed,
// arrow functions are great for compact callback functions
arrayOfNumbers.reduce((accumulator, currentValue) =>  accumulator  +  currentValue  *  2);
// another example, using a map to make a new array by
// evaluating each array element using an arrow function
const  doubledArray  =  arrayOfNumbers.map((element) =>  element  *  2);
Output:
6
11
[2,4,6]

Arrow functions make great callback functions because the syntax is nice and compact and is readable, even when used as an argument in a function.

Arrow functions allow you to write very sleek callbacks with only a light dusting of syntax.

Arrow function Gotchas

Like most features in JavaScript, the arrow function syntax does come with some caveats, let's take a brief look at them.

Arrow functions cannot be constructors

Arrow functions cannot be used to create object constructors, and that applies to both new-style ES6 constructors that use the constructor keyword and old-style prototype classes that use a function to wrap data and logic into a constructor.

As a result, you cannot call new on an arrow function, when you want to create constructors I recommend you just use the constructor keyword and favour the new style ES6 classes over traditional functional classes in general.

You also cannot call super() inside of an arrow function because arrow functions cannot be constructors.

Arrow functions have no arguments object

Arrow functions do not receive an arguments object inside the function body as regular functions do. Traditionally you would use the arguments object to iterate or interrogate a function's arguments as opposed to referencing them in the parameter list, but you cannot do this with arrow functions as the arguments array isn't defined in arrow functions

You can still access arguments using Rest arguments (...), this allows you to reference an indeterminate number of function arguments as an array.

Rest arguments will give you all you need to access the arguments in an arrow function as an array, and the syntax is pretty easy to pick up too.

Writing rest arguments is a little more declarative than using the arguments array, which is a little magic and hidden away anyway.

Pssst... if you would like to learn more about rest arguments, check out my article that covering the Rest and Spread (...) operators here.

Do not use arrow functions as object methods

You should not use arrow functions to define a method on an object, the JavaScript interpreter will let you do it, but you'll end up with unintended results because the function will not bind correctly to the instance of the object the method is supposed to be attached to.

You can workaround this by first declaring a member variable to the object using the form:

this.nameOfMethod = () => {}

Then reference the arrow function via that reference, but there's really no need to do this because ES6 Classes already have a shorthand for writing Object methods which will already bind this for you, and that is simply to write the function without specifying the function keyword.

You get the double benefit of not needing to write the function keyword and the JavaScript interpreter will automatically bind your method to this for you.

You can still declare and call arrow functions within Object methods and the Objects this will be passed down into the arrow function, so they are far from redundant or forbidden inside of classes, they just can't be methods.

Browser support

Arrow functions are well supported in the latest builds of all modern browsers and have been for quite some time, so it's pretty safe to use them in your code if you do not need to support legacy browsers.

If you do need to support legacy browsers, such as Internet Explorer, you will need to use a transpiler like Babel in your build process to ensure backward compatibility, since arrow function syntax cannot be polyfilled to work in older browsers.

If you don't need to support legacy browsers, then it's best to use arrow functions natively.

Start using arrow functions with confidence right now!

So there we have it, now you know what arrow functions are, how to use them and most importantly when and when not to use them.

I hope that this article has given you a clear understanding of Arrow functions and that you start using them in your code today!

If you liked this article, why not check out The official Sharpen Up! YouTube Channel for even more programming-related content.