What is "this" in JS?

What is "this" in JS?

ยท

7 min read

This is the one question that confuses almost every beginner in JavaScript.

And to which the usual response is: You-Are-A-Good-Question-meme.jpg

Hello, my name is Shivam Verma and today I will help you understand this keyword in a very simple way. So stay tuned with me till the end of this blog so that you don't end up answering like the man in the meme above.

Let's begin.

Well, there cannot be one specific quoted definition for the question because the value of this is not fixed in JavaScript code. Its value is more based on where and how it is being used in the code.

We will look at different cases one by one.

this in global scope.

console.log(this)

If we write this code in the browser console straight away then the output will be the window object, because this in global scope points to the global object which is window in the case of the browser. (we will be using browser console for our examples, so the global object will refer to window, in node it can be globalThis)

In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.

this in a function.

In most cases, the value of this is determined by how a function is called (runtime binding). It can't be set by assignment during execution, and it may be different each time the function is called.

call() and apply() methods are used to set the value of this while calling the function.

However, ES5 introduced the bind() method to set the value of a function's this regardless of how it is being called.

We will deep dive into these methods shortly.

Let's see some examples first:

function f1(){
    console.log(this)   // window
}

f1();

Since the code is not in strict mode, and because the value of this is not set by the call, this will default to the global object, which is window in a browser.

In strict mode, however, if the value of this is not set when entering an execution context, it remains as undefined, as shown in the following example:

function f2() {
  'use strict'; // see strict mode
  return this;
}

f2() === undefined; // true
  • this inside an object method
let obj = {
  firstname: "Shivam",
  lastname: "Verma",
  logger: function(){
            console.log(this.firstname + " " + this.lastname)  // Shivam Verma
          }
}

obj.logger()

Here the logger method refers to the obj object as it is used to call the method. And the console.log prints the value of firstname and lastname from the obj as the this is referring to obj.

One simple thumb rule to go by is that the value of this in the method is the object used to call the method before the dot.

But the above code cannot be considered ideal because what if we have 100s of such objects of users, in that case having a logger method for each object just does not make sense.

In that case, creating a common method for all objects is a better option that will reduce lines of code drastically.

let obj = {
  firstname: "Shivam",
  lastname: "Verma",
}
function logger(){
    console.log(this.firstname + " " + this.lastname)
}

Now you might be thinking that how are we gonna call the logger function as it is no longer an object method that can be accessed via the dot notation.

Here comes the savior.

  • call(), apply() and bind()

1) call()

The call() method is used to refer a value to the method/function at runtime

Eg:

let obj = {
  firstname: "Shivam",
  lastname: "Verma",
}
let obj = {
  firstname: "Tanay",
  lastname: "Pratap",
}

function logger(){
    console.log(this.firstname + " " + this.lastname) 
}

logger.call(obj)   // Shivam Verma
logger.call(obj2)  // Tanay Pratap

When we invoke a function with call(obj) the this inside the function now refers to the passed object.

Similarly, if we want to pass some arguments then we can pass them as well with call(). In that case, the first value is considered as a reference to this and other arguments are passed on to the parameters.

Eg:

let obj = {
  firstname: "Shivam",
  lastname: "Verma",
}

function logger(age, country){
    console.log(`Name: ${this.firstname} ${this.lastname}, Age:  ${age}, Country: ${country}`) 
}

logger.call(obj, 22, "India")   // Name: Shivam Verma, Age:  22, Country: India

We can pass as many arguments as we like but as per the convention it is not considered good practice to pass so many arguments to a function. That's where the apply() method becomes handy.

2) apply()

apply() works the same as call() - it also passes a reference to this of the function it is invoking. The only difference is that it takes a list/array of arguments as the second argument and passes the arguments to the function being invoked.

Eg:

logger.apply(obj, [ 22, "India" ])

3) bind()

Remember I said earlier that the value of this can't be set beforehand and it is determined by how a function is called. ES5 gave us a workaround for that via the bind() method.

The bind() method creates a new function with the same body as the invoking function, but it binds the value of this inside the function to the first argument it receives.

This returned function can later be called as and when we like and it will store the value of this which it was bound to.

Eg:

const obj = {
    name: "Shivam"
}

function logger(){
   console.log(`${this.name} says hello !`)
}

const myLogger = logger.bind(obj)

myLogger()  // Shivam says hello !
  • Problems with the method of an object and this

As we have seen that the this method inside an object points to the object it is called with or to the referred object in case of call() or apply() for a common method.

But the problem arises when there is a nested function inside the method itself.

In that case, the this inside nested function again starts referring to the window object.

Eg:

obj = {
    firstname: "foo",
    lastname: "bar"
}

function logger(){
    function test(){
        console.log(this) // window
    }
    test()
    console.log(this.firstname + " " + this.lastname) // foo bar
}

logger.call(obj)

The old school workaround for this problem was to store the current value of this into another variable and use it inside the nested function.

obj = {
    firstname: "foo",
    lastname: "bar"
}

function logger(){
    let currentThis = this
    function test(){
        console.log(currentThis) //  { firstname: "foo", lastname: "bar" }
    }
    test()
    console.log(this.firstname + " " + this.lastname) // foo bar
}

logger.call(obj)

Now it works absolutely fine as expected.

I mentioned the old school way because there is a modern way to make it work too. This brings us to the next section.

I hope you are not feeling sleepy till now ๐Ÿ˜‚, I promise there is not much left.

Moving on to the next part

arrow functions and this

In arrow functions, this retains the value of the enclosing lexical context's this. In global code, it will be set to the global object.

In simple words, the arrow function has no this of its own. It always borrows the value of this from the scope in which it is written i.e the parent's scope.

That solves our earlier problem if we use an arrow function as the nested function of the method, then we won't need to store currentThis in a variable.

function logger(){
    let test = () => {
        console.log(this) //  { firstname: "foo", lastname: "bar" }
    }
    test()
    console.log(this.firstname + " " + this.lastname) // foo bar
}

That is one of the reasons people started loving arrow functions.

Just keep in mind that you don't make the method function as an arrow function because if you do so no matter what you do the this will be pointing to window object all over again.

As promised that was all about this from my side for the blog.

Finally we come to an end.

Hope you liked it. If so feel free to share it among your peers.

Now you can say that you understand some parts of this keyword in JavaScript.

Wait what, did he just say "some parts" after such a long blog ?

Yes I did, we haven't seen working of classes and this yet ๐Ÿ˜‚.

Let's do it some other day. Let me know in the comments if you would like me to write the second part of it.

Till then keep coding.

You can connect with me on Twitter.