"this" Keyword

"this" Keyword

Welcome to the madness!

Hi, I hope you are doing well, A lot of times I have encountered people who consider JS as some sort of' bandage fix' language, some say it is a duct tape of the internet, the people that say these words don't appreciate the beauty of this language, but I do and would always want that you should also do.

But why do people call this beautiful language with all these names, there are multiple reasons, and one of the major reasons is the this keyword.

Before starting onto the this keyword I would like to tell you a small thing that will be handy throughout this blog, so there is a thing called global object, which differs from one place to another according to the environment in which JS is running, also sometimes depends on the mode in which JS is running i.e strict mode. If you want I can write a separate blog regarding all of these, but for the time being, all the examples that I will be showing in this blog are -

  • Executed on the browser.
  • strict mode is not enabled.

which in turn means, that the global object is the window object, which I might sometimes refer to as a bad this, please bear with me, so without further ado let's dive right into it.

I will tell you the basic reason why this becomes a tough thing to understand because it changes from one place to another, it is contextual, it changes with the involvement of others.

Let's start with the easy things first. Try executing the following code in the browser (console).

console.log(this)
if(true){
console.log(this)
}

The above code will print the global object in this case, the widow object twice. Things to learn from this-- this doesn't change outside a function and this doesn't depend on any block of code. Screenshot 2022-01-22 190302.png

Now let's get the party started, try to guess the output of the following piece of code.

function walk() {
return `${this.fName} started walking`
}

const me = {
 fName: "Mihir",
 walk,
}

console.log(me.walk())
console.log(walk())

Strange right -

  • me.walk() logs -- Mihir started walking
  • walk() logs -- undefined started walking

This particular thing happens because in the first case the walk() is associated with an object me which has a property as fName so when we call me.walk(), the this inside the walk() is resolved as the object me, in other words, the this points to the object me inside walk() when invoked using me.walk().

Although this is not the use case when you invoke the function directly. In that case, the this inside the walk() is the window object, and that doesn't have any property called fName, that is why this.fName is resolved as undefined.

To prove the above-mentioned point, we will console.log(this) inside the walk() function.

Screenshot 2022-01-22 193803.png

If you have an object that you can't alter, you can still bind our good old walk() with that object and use it. You can do this with the methods -- call,bind,apply. These functions are present on the Function.prototype, If you want I can write a blog post of how these functions work internally, but for the time being, we will just use it to our power.

function walk(){
 return `${this.fName} started walking`
}

const FixedMe={
 fName: "Mihir"
}

const meWalk=walk.bind(FixedMe)
meWalk()

Now, why does this work? I will help you, so the bind() returns a new function with the this in the function pointing to the object which you passed as an argument to the bind().

Now let's see how this works inside a constructor function --

function Person(n,a) {
 // Properties
 this.name=n; 
 this.age=a;
}

// Methods
Person.prototype.walk=function() {
 return `${this.name} started walking`
}

const me=new Person("Mihir",21)
me.walk();

Now, let's understand how this works over here, but before understanding that you need to understand what new does over here, this keyword new is such a powerhouse, basically, it follows a 4 step process, but let me tell you the mental model for it. It basically creates a new object binds it with the this inside the Function and returns that object. To prove this point I can do console.log(JSON.stringify(this)), and you will see a {}. If you want I can write a blog regarding the new keyword, in that I can deep dive into the 4 step process of what the new keyword does.

Screenshot 2022-01-22 220012.png

Here I have used JSON.stringify(this) as this object is later assigned to me, inside that the name and age property is also updated, as it is a reference, inside the dev tools you will also see the name,age property if I just console.log(this), to know exactly what was this at the particular moment, I changed the object to a string, which shows the actual representation at that particular moment.

Now let's talk about callback functions, let me dive into an example --

function Person(n) {
 this.fName=n

 setTimeout(function() {
  console.log("Inside cb()");
  console.log(this.fName);
  }, 100)
}

const me=new Person('Mihir')

Guessed the output, or you are pulling your hair right now? Let me explain so what happened over here is, the callback function actually doesn't run in the same context as the Person(), it moves out of the main thread and actually executed later. I know it went a little wild out here, moving out of the main thread, event loop, I would be very happy to write about all of this too, but currently not in the scope of this particular blog. For the time being, just think that the callback function is not executed in the Person() context. So the this becomes the global object i.e window and there is no property fName on that, so it shows undefined. So how to solve this problem? Two ways --

setTimeout(function() {
  console.log("Inside cb()");
  console.log(this.fName);
 }.bind(this), 100)

Screenshot 2022-01-22 234837.png

Here we used the bind() and associate this particular function with the this, and this -- this is actually present in the context of Person() after the callback function is completely declared, the context of Person() starts, and that my friend is the big game happening.

Screenshot 2022-01-22 234837-2.png

The area marked in red is the callback function that is not in the scope of Person()

Another way to fix it? Here we go --

setTimeout(()=> {
  console.log("Inside cb()");
  console.log(this.fName);
 }, 100)

Screenshot 2022-01-22 235505.png

Here we used the new ES6 arrow function, and it works why? Because over here the this is actually the same this present inside the context of Person().

The arrow function creates an auto-binding of this, with the this that is present inside the surrounding scope, at the time of object creation.

I know it is a lot to take, and the arrow function example probably blew your mind. You can check out this blog -- "when not to use arrow functions"

If you made it through to this, congrats! I will make things easier for you after this, so how do you resolve this? let us summarize

Summary

  1. If the new keyword is used when calling the function, this inside the function is a brand new object.
    function ConstructorFn() {
     console.log(JSON.strigify(this)); // {}
     this.value = 10;
     console.log(this); // { value: 10}
    }
    new ConstructorFn();
    
  2. If apply, call, or bind are used to call a function, this inside the function is the object that is passed in as the argument to the function calls above.
    function fn() {
     console.log(this);
    }
    const obj = {
     value: 5
    };
    const boundFn = fn.bind(obj);
    boundFn();     // { value: 5 }
    fn.call(obj);  // { value: 5 }
    fn.apply(obj); // { value: 5 }
    
  3. If a function is called as a method — that is, if dot notation is used to invoke the function — this is the object that is used to call the method (left side of the dot).
    const obj = {
     value: 5,
     printThis: function() {
         console.log(this);
     }
    };
    obj.printThis(); // -> { value: 5, printThis: ƒ }
    
  4. If a function is invoked as a free function invocation, meaning it was invoked without any of the conditions presented above, this is the global object, i.e window.
    function fn() {
     console.log(this);
    }
    // If called in browser:
    fn(); // Window {stop: ƒ, open: ƒ, alert: ƒ, ...}
    
  5. If multiple of the above rules applies, the rule that is higher wins and will set the this value.

    const obj1 = {
     value: 'hi',
     print: function() {
         console.log(this);
     },
    };
    const obj2 = { value: 17 };
    

    If rules 2 and 3 both apply, rule 2 takes precedence -->

    obj1.print.call(obj2); // { value: 17 }

    If rules 1 and 3 both apply, rule 1 takes precedence -->

    new obj1.print(); // {}

  6. If the function is an ES6 arrow function, it ignores all the rules above and receives the this value of its surrounding scope at the time it’s created.

Woohoo! Now you have become a this god. Thank you for bearing with me for so long. Thank you and have a nice day!

Reference - Set of rules for this