"this" Keyword in Arrow Functions

Murtuzaali Surti
Murtuzaali Surti

• 4 min read

The this keyword in javascript is probably one of the most misunderstood concepts of javascript. There are already some brilliant resources out there to explain the this keyword, but in this article, you will see how the this reference is different in arrow functions introduced in ES6 as compared to regular function expressions. You will also get to know a way to visualize the this keyword reference inside arrow functions.

This article will only cover implicit binding of the this keyword, i.e. what's interpreted by javascript by default.

Let's start by defining an object named user. In this object, you have a regular function expression named logName. How will you call the logName function if you want to execute it? You will have to do user.logName().

const user = {
    name: "John",
    logName () {
        console.log(this.name);
    },
};
user.logName(); // logs 'John'

Notice the object on which the function is invoked. The object on which it is invoked is user and that's the execution context of the function logName. So, whenever you type this inside the regular function expression, it automatically refers to the object that it's being called upon. In this case, it will log the value of the name property inside the object user. In other words, it binds to the context of the object user.

Now, let's create an arrow function logNameArrow() inside the same object and having the same body as logName().

const user = {
    name: "John",
    logName () {
        console.log(this.name);
    },
    logNameArrow: () => {
        console.log(this.name); 
    },
};
user.logName(); // logs 'John'

What do you think will be logged when you invoke logNameArrow? It will log undefined but why? That's because arrow functions don't have their own bindings.

In other words, even if you invoke the arrow function from the object user like this user.logNameArrow(), the arrow function doesn't know on what it got invoked upon. Instead, what it knows, is the lexical scope in which it exists. And, it will bind to the context of the closest enclosing scope (in this case it's the window object).

Lexical Scoping defines how variable names are resolved in nested functions: inner functions contain the scope of parent functions even if the parent function has returned. - Pierre Spring, stackoverflow.com

const user = {
    name: "John",
    logName () {
        console.log(this.name);
    },
    logNameArrow: () => {
        console.log(this.name);
    },
};
user.logName(); // logs 'John'
user.logNameArrow(); // logs undefined

It's similar to how variables are resolved using lexical scoping. If a variable is not defined in the current scope, javascript will check the parent scope for that variable, and will keep doing so until it reaches the highest parent scope which encloses everything.

To test this, you can create a new arrow function inside the regular function expression logName.

const user = {
    name: "John",
    logName () {
        console.log(this.name);
        const logNameArrow = () => console.log(this.name); // logs 'John'
        logNameArrow();
    },
    logNameArrow: () => {
        console.log(this.name); // logs undefined
    },
};

Now, here comes the interesting part. Since arrow functions don't have their own bindings, they look for the parent scope's context binding, in this case it's the context binding of the regular function expression logName(). That is the reason the arrow function logNameArrow (inside the logName regular function expression) will log 'John' instead of undefined. Here's an example which demonstrates lexical scoping which is three levels deep.

const user = {
    name: "John",
    logName () {
        console.log(this.name); // John

        const logNameArrow = () => {
            console.log(this.name); // John

            const logNameArrow2 = () => {
                console.log(this.name); // John

                const logNameArrow3 = () => {
                    console.log(this.name); // John
                }
                logNameArrow3();

            }
            logNameArrow2();

        };
        logNameArrow();
    },
    logNameArrow: () => {
        console.log(this.name) // undefined
    },
};
user.logName();
user.logNameArrow();

Let's visualize the highest enclosing lexical scope by wrapping this object in an IIFE (Immediately Invoked Function Expression).

(function () {
    this.name = 'Peter'

    const user = {
        name: "John",
        logName () {
            console.log(this.name); // John
        },
        logNameArrow: () => {
            console.log(this.name) // Peter
        },
    };
    user.logName();
    user.logNameArrow();
})();

When you wrap the object in a wrapper function, it creates a new lexical scope in which the object and its properties exist. Define a variable named name, identical to the user object property name and assign it a different value. Now, as you know, the arrow function tries to find the closest enclosing parent lexical scope (which in this case is the highest enclosing parent scope) i.e. the scope of the wrapper IIFE. And, it finds a variable named name in that scope. And, thus it logs 'Peter' as a result.

That was everything you needed to know about how the this keyword is referenced implicitly in javascript arrow functions.

Must Reads 🚨


5 Newsletters Every Developer Should Read

Previous

Server Sent Events 101

Next