Home TutorialsModern ES6 Syntax Arrow Function Expresssions

Arrow Function Expresssions

by TheScriptKiddie

Arrow Function Expressions

Arrow function expressions, AKA arrow functions, big-arrow functions, etc have a shorter syntax compared with a regular function expression. Perhaps one of the biggest differences in an arrow function is that it does NOT have its own this value.

How does it look?

You will see arrow functions take on many forms in the wild. The main identifying characteristic is the “arrow” created with an equal sign and greater-than sign. Here are some examples:

let isEven = (value) => {
    return value % 2 === 0;
}

isEven(2); // true
isEven(3); // false

btn.addEventListener('click', (e) => {
    console.log('clicked!');
});

A Master of Disguise

The arrow function has two short-hands that can make it difficult to understand what exactly is going on. These short-hands are popular and chances are you will see them in the wild, so it’s important to be able to understand them.

No Parenthesis for Single Parameters

When are arrow function has a single parameter, you may omit the parenthesis around the parameter list. Note that parenthesis are still required for 0 or 2+ parameters:

let isEven = value => {
    return value % 2 === 0;
}

let greet = () => {
    console.log('Hello!');
}

let add = (a,b) => {
    return a + b;
}

isEven(2); // true
isEven(3); // false
greet();
add(5,7);

No Curly-Braces for Immediate Returns

Sometimes your arrow function is simple, and just needs to return an easily computed value. When that is the case, you can omit the curly braces around the body of the arrow function. This means that whatever value/expression that appears immediately after the arrow will be returned.

let isEven = (value) => value % 2 === 0;
let add = (a,b) => a + b;

// Immediate return shorthand, but immediately returning an object literal
// When this is the case, the curly braces of the object must be wrapped in parenthesis (otherwise, the engine couldn't distinguish between the curly braces being for a function body, or an object literal)
// This arrow function is returning an object literal with 4 properties:
let getStats = (a,b) => ({
    sum: a + b,
    difference: a - b,
    product: a * b,
    dividend: a / b
});

let stats = getStats(2,3);

isEven(2); // true
isEven(3); // false
add(5,7);

All Together Now

You can combine the two short-hands as well.

let isEven = value => value % 2 === 0;
let add = (a,b) => a + b;
let getName = value => value.name;

isEven(2); // true
isEven(3); // false

let obj = {
    name: 'Jane',
    age: 27
};

getName(obj); // 'Jane'

The ‘this’ Borrower

Arrow function expressions do not have their own this value. That doesn’t mean you can’t use this in an arrow function. It just means that when you do, the value of this in the surroundings of the arrow function will be used.

Before arrow functions existed, mistakes like below were common:

class Person {
    constructor() {
        this.age = 0;
    }

    growUp() {
        setInterval(function() {
            this.age++;
        }, 1000);
    }
}

let p = new Person();
p.growUp();

In the function invoked by setIntervalthis does not mean what we think it means. It actually refers to the global object (window in the browser). Because the window does not have a variable called age, this expression will fail.

There are several ways to fix this issue.

  1. Explicitly binding the value of this (notice the .bind(this)):
class Person {
    constructor() {
        this.age = 0;
    }

    growUp() {
        setInterval(function() {
            this.age++;
        }.bind(this), 1000);
    }
}

let p = new Person();
p.growUp();

Why does this work? Because we are no longer allowing the anonymous function to use the default this value. Instead we are manually setting it to this at this moment in time. Confusing right? Here’s another way of looking at it:

class Person {
    constructor() {
        this.age = 0;
    }

    growUp() {
        let ageIncrementer = function() {
            this.age++;
        }.bind(this);
        setInterval(ageIncrementer, 1000);
    }
}

let p = new Person();
p.growUp();

OR:

class Person {
    constructor() {
        this.age = 0;
    }

    growUp() {
        function ageIncrementer() {
            this.age++;
        }
        setInterval(ageIncrementer.bind(this), 1000);
    }
}

let p = new Person();
p.growUp();

As you can see, there are many ways to accomplish this. But the main takeaway is that we are preparing a version of the ageIncrementer function with its this value PRE-BOUND to a specified value (this, at the time growUp was called) instead of a dynamically determined value (this, at the time setInterval decided to call the ageIncrementer).

  1. USING AN ARROW FUNCTION
    Now that we have arrow functions, this is much simpler:
class Person {
    constructor() {
        this.age = 0;
    }

    growUp() {
        setInterval(() => {
            this.age++;
        }), 1000);
    }
}

let p = new Person();
p.growUp();

This “just works” because the arrow function does not have its own dynamic this value. Instead, it looks to its surroundings. In this case, that is the growUp method, which has a current this value p, the Person instance because growUp was called off of p:

p.growUp();

You may also like

Leave a Comment