14 Feb 2021

Anonymous functions VS arrow functions in JavaScript

Some JS developers don't know the differences between anonymous functions and arrow functions, and this misunderstanding sometimes leads to unintentional behavior.

You have to know that the anonymous function isn't the same as the arrow function, and to make that clear, let's have a look at the following Vue.js component:

<template>
    <div>
        <ul>
            <li v-for="user in users">{{ user.first_name }} {{ user.last_name }}
</li>
        </ul>
    </div>
</template>

<script>
export default {
    name: "Users",
    
    data() {
        return {
            users: [],
        }
    },
    
    mounted() {
        axios('https://reqres.in/api/users').then(response => function() {
            this.users = response.data.data;
        })
    }
}
</script>

As you've noticed, the users should be populated with the response.data.data - as soon as the component gets mounted - , but it still and will always refer to an empty array.

So, what’s the problem?

The problem is that the this keyword doesn’t get rebound when using anonymous functions, let’s see what does that mean by inspecting the this as follows:

mounted() {
    axios('https://reqres.in/api/users').then(function(response) {
        console.log(this);
    })
}

Output:

Window {window: Window, self: Window, document: document, name: "", location: Location, ...}

As you’ve noticed, the console.log(this) returns the Window object and not the Vue object - which, it should be because it’s running within a Vue instance - .

Prior ES6, JS developers tend to solve this problem by declaring a variable that binds the this outside the anonymous function as follows:

const self = this;
axios('https://reqres.in/api/users').then(function(response) {
	console.log(self); // Vue instanse
})

Another solution is to use the bind method:

axios('https://reqres.in/api/users').then(function(response) {
	console.log(this);
}.bind(this)); // Binds Vue instanse

While both solutions work as expected, they seem to be quirky, therefore ES6 introduced the arrow functions.

Arrow functions will always bind the current this so you don’t need to use any of those tricks:

axios('https://reqres.in/api/users').then(response => this.users = response.data.data);

Additionally, arrow functions use shorter syntax hence less code and more readability:

const langs = ['PHP', 'JavaScript', 'Go', 'Python'];

// Short syntax, no need for parantheses and return statement. Only one line supported.
langs.map(lang => `I love ${lang} !`);

// Multiple arguments, you should use parantheses.
langs.map((item, key) => `${key} I love ${lang} !`);

// Multiple lines
langs.map((lang, key) => {
    // more lines goes here
    return `${key} I love ${lang} !`;
});

As I said, arrow function isn’t a replacement for anonymous function, let me explain that by an example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Events</title>

    <style>
        .style2 {
            background-color: coral;
            color: white;
            font-weight: bold;
        }
    </style>
</head>
<body>

<div id="app">
    Hello World
</div>

<script>
    const app = document.getElementById("app");

    app.addEventListener('click', function(event) {
        this.classList.toggle('style2');
    });
</script>
</body>
</html>

This example works perfectly, because the this refers to the app element and not the Window object, this means that style2 will be toggled when the user clicks on the app element.

Let’s try to replace it by the arrow function:

app.addEventListener('click', () => {
    this.classList.toggle('mystyle');
});

Output:

Uncaught TypeError: Cannot read property 'toggle' of undefined ...

As you guessed by now, the arrow function refers to the Window object and not the app element.

I hope you enjoyed reading this short post!