Javascript this 101

November 13, 2019

this is one of the most common JS keywords. You see them everywhere, but it can be hard to tell what this is.

I will cover 3 scenarios where this may be used: globally, inside a regular function, and inside arrow function. This should cover most usage.

  1. Global this
  2. this inside a regular function
  3. this inside arrow function

Let's start by looking at some examples!

Btw, I will be doing this inside browser (chrome) console, not node module. I will also assume strict mode is not used.

Global this

If we just type this in our browser console, it will refer to window/ global object.

this // Window {...}

var helloVar = 'helloVar'
this.helloVar // helloVar

window.helloWindow = 'helloWindow'
this.helloWindow // 'helloWindow'

const helloConst = 'helloConst'
this.helloConst // undefined

let helloLet = 'helloLet'
this.helloLet // undefined

You see that let and const can't be called through this. They are not stored inside "object environment record", but inside "declarative environment records". Explaining this will be outside of this article's scope. Here's a link if you're interested.

this inside a regular function

Let's start by an example:

const obj = {
    breakfast: 'donut',
    wutBreakfast: function() {console.log(`I had ${this.breakfast} this morning!`)}
}
window.breakfast = 'waffles';

obj.wutBreakfast() // I had donut this morning!

Here we observe that this inside this.breakfast refers to the object itself. Look at where the function call is when calling obj.wutBreakfast(). Ask yourself: "Is there an object to the left of my function call?" That object is where your this refers to.

What if there is no object to the left of function call? If you are calling a function without an object to the left of function call, you can assume it is the global object. In this case, the Window object. Let's look at the next example:

function sayBrunch(){
    console.log(`I had ${this.brunch} for brunch!`)
}

sayBrunch() // I had undefined for brunch

We haven't defined anything for brunch yet, so it returns undefined. Let's define it inside window object

window.brunch = 'oatmeal'
function sayBrunch(){
    console.log(`I had ${this.brunch} for brunch!`)
}

sayBrunch() // I had oatmeal for brunch!

Let's do few more examples to build your intuition:

window.dinner = 'pizza'
const foodObj = {
  dinner: 'spaghetti',
  sayDinner: function(){
        console.log(`I had ${this.dinner} for dinner!`)

  }
}
foodObj.sayDinner() // what does it return?

Another one, with a little twist. We defined a window appetizer string and a mealObj.appetizer string. We call sayAppetizers from two different objects. What do you think each will return?

window.appetizer = 'chocolate';

function sayAppetizer(){
    console.log(`I had ${this.appetizer} for appetizer!`)
}
const mealObj = {
  appetizer: 'ice cream',
  sayAppetizer: sayAppetizer
}
mealObj.sayAppetizer() // what does it return?
sayAppetizer() // what does it return?

Just remember, this inside regular JS function refers to the object immediately to the left where the function is called. If there is no object, assume it is a window object.

With this in mind, even if we have obj1.obj2.obj3.someFunc(), we know that this inside someFunc() will refer to obj3 because it is the closest object to where function is called.

this inside arrow function

This behaves differently inside an arrow function. There are three things you need to keep in mind the whole time:

  1. Only regular function and global function can have this.
  2. Arrow function does not have this on its own
  3. When this is referred to inside an arrow function, it will look up the scope to find this value. It behaves like lexical scope.

Let's look at first example:

let myObj = {
  breakfast: 'taco',
  sayBreakfast: () => {
    console.log(`I had ${this.breakfast} for breakfast`)
  }
}
window.breakfast = 'pizza'

myObj.sayBreakfast() // pizza

Let's see if this makes sense while keeping the 3 rules above in mind: when we call myObj.sayBreakfast(), it looks up to myObj, but since myObj does not have this (rule #2), it will look one more up, the global/ window object (rule #1). It saw that global/window has this.breakfast = 'pizza', so it prints pizza.

Now add a regular function to object:

let myObj = {
  breakfast: 'taco',
  sayBreakfast: () => {
    console.log(`I had ${this.breakfast} for breakfast`)
  },
  sayRegBreakfast: function() {
    console.log(`I had ${this.breakfast} and it was yummy`)
  }
}
window.breakfast = 'pizza'

myObj.sayBreakfast() // pizza
myObj.sayRegBreakfast() // taco

You'll see that using regular function gives "taco" and arrow gives "pizza".

Let's call an arrow function from global object scope. We should expect it to have this from global scope. Is it true?

window.secondBreakfast = 'eggs';

const saySecondBreakfast = () => {
  console.log(`I had ${this.secondBreakfast} for second breakfast!`)
}

saySecondBreakfast() // eggs

I was in disbelief when I see this either, so let's prove it further. The example below is from getify archive:

function foo() {
    return function() {
        return function() {
            return function() {
                console.log("Id: ", this.id);
            }
        }
    }
}
foo.call( { id: 42} )()()() // undefined

vs

function foo2() {
   return () => {
      return () => {
         return () => {
            console.log("id:", this.id);
         };
      };
   };
}
foo2.call( { id: 42 } )()()() // 42

(Btw, call assigns this to function we are calling - foo/ foo2 itself - with the argument object we pass)

Remember that only arrow function looks up lexically; the first example looks for this inside the third nested function and found nothing, so it returns undefined.

While foo2, finding no this inside third nested function, lexically looks up for next available reg/ global function's this. It found foo2's this (from foo2.call({id: 42})) first (remember rule #1), so it prints 42.

If there had been a regular function on the second example earlier, it wouldn't have found it, like:

function foo3() {
   return () => {
      return function() { // this is regular function now
         return () => {
            console.log("id:", this.id);
         };
      };
   };
}
foo3.call({id:101})()()() // undefined

But if we gave this to where the return function() {...}) is, it would have found it. Because when arrow function lexically looks up and found the first regular function, that function is given this value of 101.

function foo3() {
   return () => {
      return function() { 
         return () => {
            console.log("id:", this.id);
         };
      };
   };
}
foo3()().call({id: 101})() // 101

So that's it folks! This is definitely only the tip of iceberg, but this should be enough to get you started - pun intended 😁.

Let me know if you have questions/ found mistakes - thanks for reading and happy codin'!!

More Readings: