DEV Community

Cover image for Understand "this" on JavaScript right now
Alex
Alex

Posted on

Understand "this" on JavaScript right now

this keyword has lead to a lot of bugs and troubles over the years. Nowadays there are a lot of post trying to explain how this works but I'll try to give my bit on this topic.
Before enter the good stuff we need to review a little of concepts to understand how JavaScript manages this depending on the context, so...

What we need to know before? πŸ€”

If you are familiar with the following terms maybe you can skip this section:

  • Arrow functions
  • Nested functions
  • Methods

Arrow functions 🏹

Arrow functions are a new type of functions introduced in ES6. Basically they are a shortcut way of writing functions.
Here we can see their syntax:

const square = (a, b) => a * b;
Enter fullscreen mode Exit fullscreen mode

And the same with regular functions

const square = function(a, b) { return a * b };
Enter fullscreen mode Exit fullscreen mode

Nested functions 🎎

Nesting gif

Nested functions are nothing more than functions inside another functions.
This functions can access the variables defined inside the parent function and are only visible within the scope of the parent function.

function parent(){
    function nestedFunction(){
        console.log('Hey! I am a nested function');
    }
    nestedFunction();
}
Enter fullscreen mode Exit fullscreen mode

Methods πŸ“¦

Methods are functions that are declared inside an object. This means that method are functions that need to be executed with an object as context.

const obj = {
    x: 0,
    y: 0,
    square: function() {
        return this.x * this.y;
    },
}
Enter fullscreen mode Exit fullscreen mode

Here we have a function that uses the object properties to get the square of these numbers. The function cannot work on its own.

Let's go with this keyword πŸ’ͺ

Strong Bob

We'll classify how this works like in the previous section.

Regular functions

Functions by default are bound to the global object.

function writeGlobal(){
    console.log(this);
}
Enter fullscreen mode Exit fullscreen mode

So this keyword in the previous example references to the global object.

We would bind this to another value (in these examples to a string object) using the following methods:
Bind

let sayHello = function() {
    console.log(`Hello ${this}!`);
}
sayHello = sayHello.bind('Alex');
//Prints 'Hello Alex!'
sayHello();
Enter fullscreen mode Exit fullscreen mode

Note: We have to assign sayHello function again because bind returns the function bound, not modifies the original function object

Call

let sayHello = function() {
    console.log(`Hello ${this}!`);
}

//Prints 'Hello Alex!'
sayHello.call('Alex');
Enter fullscreen mode Exit fullscreen mode

Apply

let sayHello = function() {
    console.log(`Hello ${this}!`);
}

//Prints 'Hello Alex!'
sayHello.apply('Alex');
Enter fullscreen mode Exit fullscreen mode

Arrow functions

When we use arrow functions this doesn't have the global object bound, instead it inherits the this value of the context within they are. Let's see some code.

let sayHello = function() {
    const getHelloPhrase = () => {
        return `Hello ${this}!`;
    }

    console.log(getHelloPhrase());
}

//Prints 'Hello Alex!'
sayHello.call('Alex');
Enter fullscreen mode Exit fullscreen mode

Here, the arrow function uses the this value to generate a hello string. So when we bound the this value of sayHello function to 'Alex', the arrow function also has the this value bound to 'Alex'

Nested functions

Nested functions works a little bit different as the previous example even though it looks pretty similar. Let's see some code before explaining.

let sayHello = function() {
    const getHelloPhrase = function() {
        return `Hello ${this}!`;
    }

    console.log(getHelloPhrase());
}

//Prints 'Hello [object global]!'
sayHello.call('Alex');
Enter fullscreen mode Exit fullscreen mode

Here we have a pretty similar code like before but we get a different result 😟
Why?
Unlike arrow functions, nested functions don't inherit the this value of the context they are declared.

Like we saw before, with regular functions the value is bound to the global object, and since nested functions are regular functions inside an others, they have the same rules.
So if we want nested functions to have the same behavior as arrow functions we need to take the following approach.

let sayHello = function() {
    let getHelloPhrase = function() {
        return `Hello ${this.toString()}!`;
    }

    getHelloPhrase = getHelloPhrase.bind(this);
    console.log(getHelloPhrase());
}

//Prints 'Hello Alex!'

sayHello.call('Alex');
Enter fullscreen mode Exit fullscreen mode

Now we have the same behavior because we are binding the nested function to the this value of the parent function (like arrow functions).

Methods

Earlier I've said that regular functions has this value as the global object by default, but there is an exception as we are going to see right now. So considering the following code.

let person = {
    name: 'Alex',
    sayHello: function() {
        console.log(`Hello ${this.name}!`);
    }
}

//Prints 'Hello Alex!'
person.sayHello();
Enter fullscreen mode Exit fullscreen mode

You may think that this would print Hello undefined! since the global object doesn't have a property called name, but with objects we have a different behavior.

When we declare a function inside an object (a method) JavaScript automatically binds the object as the context of the function. So when we refer to this inside a method we are referring to the object that has the method. That explains why we cat get the property name of the person object with this.name.

The following code show a similar of what JavaScript does under the hood.

let person = {
    name: 'Alex'
}

function sayHello() {
    console.log(`Hello ${this.name}!`);
}

//Prints 'Hello Alex!'
sayHello.call(person);
Enter fullscreen mode Exit fullscreen mode

So... If we use arrow functions instead of regular functions they behaves the same ? πŸ€”

Wrong ❌

You should't use arrow functions as member functions. Why?
As seen earlier, arrow functions behaves a little different to regular functions. They inherits this from the scope within they are declared.

See this page for more details.

Conclusion

The behavior of this keyword may appear a little bit hard to learn at first but if you break down the different behaviors depending on the context maybe you'll understand better how this works.

This is the way I understood the behavior and I hope that maybe this post will help you to understand too.

Top comments (0)