"This" in Javascript Explained

Ever heard this in javascript? Yeah, that weird keyword. When i dedicate my time learning Javascript fundamental, this make me scratch my head a lot.

If you’re from React engineer, you might be used it in React Class Component era (this.state.count remember?), and we might be don’t care what it actually does. If i successfully get my state, so what?🤷‍♂️

But if you’re preparing for frontend interview, chances are you’ll get asked about this. So in this article, i want to share my learnings with some case study as we go deeper to fully understand how this works.

At the end of this article, i also give you some exercise and explained answer. Try to solve by yourself, predict the output, explain it out loud!💪

So, what is this?

The keyword this refers to the value of the object, depend on where this is called. It can be called anywhere: in the global context, function, and a method wether in object or class. this in each of them behaves differently and thus will have a different value.

confuse?😆 don’t worry, keep reading. I have some trick for you later. For now, let’s assume that this refer to an object. The question is, what object? let’s dive in!

this inside Global Context

Global context is where your code is not inside any function, class, or block statement. When you call this in the global context, it refer to global object. The object is depends on your environment you run your code: global in Node, window in Browser

this.name = "Panda"

console.log(this.name); // output: Panda
console.log(this); // output: { name: "Panda", ...global (or window) }

Keep in mind, in simple terms, the most outer object that this can refer to is a global object.

this inside Function Context

First we have to divide function into two: Regular Function and Arrow Function. Why? because this in these two behave differently.

  • Regular function is a function in Javascript before ES6 (ES5 and below).

  • Arrow Function is a new way to declare function in Javascript ES6.

We have to understand this behavior in each one of these function declaration.

Regular Function

There is two way of calling Regular Function that this will behave differently: pure function call and object method call.

Let’s see what pure function call does

function getPerson() {    
    this.name = "John"
    console.log(this.name)
}

getPerson() // output: John

In non-strict mode, when you call a function, this inside that function refer to global object. So this.name = “John” will set the global property name to John.

To prove it, run these code below

function getPerson() {
  this.name = "John";
  console.log(this.name);
}

function getName() {
  console.log(this.name);
}

getPerson(); // output: John
getName(); // output: John

You can see both output is John. getName didn’t even declare this.name inside the function. Lets break it down:

  1. This is non-strict mode

  2. getPerson() is called

  3. this.name = “John” is set to global context.

  4. console.log(this.name) → this try to find name in global context. Found John, then print it

  5. getName() is called

  6. console.log(this.name) → this try to find name in global context. Found John (set in getPerson() call), then print it

Let’s twist a bit, how about this?

function getPerson() {
  this.name = "John";
  console.log(this.name);
}

function getName() {
  this.name = "Ryan";
  getPerson();
}

getName(); // output: John

Pay attention to the execution order, lets break it down:

  • This is non-strict mode

  • getName() is called

  • in getName function → this.name = Ryan is set. Now name is Ryan.

  • getPerson() called

  • in getPerson function → this.name = John is set. Now name is replaced with John

  • console.log(this.name) → will print John — recall of name is replaced from Ryan to John

Remember that this in pure function call refer to global object, period. If you see 2 function call like code snippet above, just pay attention to the execution order.

However, if the function called as a method of an object, this refer to that object. This is often called implicit bound, where this is implicitly bound to an object.

const person = {
    name: "John",
    getPerson() {
        console.log(this.name)
    }
}

person.getPerson() // output: John

Even though we wrap the method call with other function, we still get the same result. No matter how nested the wrap it is.

const person = {
  name: "John",
  getPerson() {
    console.log(this.name);
  },
};

function getName() {
  this.name = "Panda";
  person.getPerson(); // this.name not replaced to Panda
}

getName(); // output: John

Remember this :

In regular function, this value depends on HOW function is called.

When function called as a pure function call, this refer to the global object. When function called as a object method, this refer to its object.

Lets practice before we jump into arrow function. Guess the output carefully

const person = {
    name: "John",
    getPerson() {
        this.name = "Brian"

        function getName() {
            console.log(this.name)
        }
        getName()
    }
}

person.getPerson() // output: ??

If you guess undefined, congrats! you understand it well enough until here👍 Let’s break it down :

  1. This is non-strict mode

  2. person.getPerson() is called

  3. in getPersonthis.name = “Brian” is set. Remember, this is a method call. So this refer to its object, in this case person object. So person.name is replaced from John to Brian

  4. getName() is called

  5. In getNameconsole.log(this.name). Remember how function called, this in regular function call always refer to global context

  6. because we haven’t set anything in global context, this.name will print undefined

Arrow Function

Arrow function do not have their own this. Instead, they lexically bind this from its surrounding context. For example, see this code snippet

const person = {
  name: "John",
  getPerson() {
    console.log(this.name);
  },
  getName: () => {
    console.log(this.name);
  },
};

person.getPerson(); // output: John
person.getName(); // output: undefined

getName is not bind into person, it bind into its lexical scope. In this code, no surrounding lexical scope found by getName. So this refer to global object.

If you want to fix the undefined, you can wrap it with regular function.

const person = {
  name: "John",
  getPerson() {
    console.log(this.name);
  },
  getName() {
    const arrowFunc = () => {
      console.log(this.name);
    };

    arrowFunc();
  },
};

person.getPerson(); // output: John
person.getName(); // output: John

getName will be arrowFunc surrounding lexical scope. Because getName is called as a person method, so this bind to person object. Then this.name would be John.

this inside Constructor Function

When function is in instantiated with new keyword, this is refer to newly created object from the constructor. For example

function Cat(name, breed) {
  this.name = name;
  this.breed = breed;
}

const myCat = new Cat("Boo", "British Short Hair");
console.log(myCat.name); // Output: Boo
console.log(myCat.breed); // Output: British Short Hair

this.name and this.breed is no longer refer to global context, unlike usual function we know before.

this inside Class

When you define a method inside a class, that method is added to the class’s prototype. When you call it using an instance of the class, this refers to that instance.

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hi, I'm ${this.name}`);
  }
}

const p = new Person("Ali");
p.greet(); // Hi, I'm Ali

💡Gotchas

One thing you must remember. When working with class, javascript implicitly uses “strict mode”. Let’s make a little twist to prove it

class Person {
  constructor(name) {
    this.name = name;
  }

  greet() {
    function speak() {
      console.log(`Hi, I'm ${this.name}`);
    }

    speak();
  }
}

const p = new Person("Ali");
p.greet();

Theoretically, code snippet above would print undefined. Thats because:

  1. new Person() instantiated, and assigned to p

  2. p.greet() called

  3. inside greet function → speak() called

  4. inside speak function → we call this.name

  5. because speak() is not a method call, this should refer to global context. So it will print undefined

However…. javascript implicitly use strict mode when working with class. And the code should like this under the hood

'use strict';

class Person { ... }

So, that code snippet will throw error

TypeError: Cannot read properties of undefined (reading 'name')

So be careful if you face a code snippet like this. Remember, class always use strict mode.

Let’s Practice!

Exercise for you is simple:

  1. Predict the output of given code snippet.

  2. Why is that? Explain it out loud!

Give yourself a try, don’t directly go to solution. Just remember the fundamental and i’m sure you good to go. Good luck😉

Exercise #1

const person = {
    name: "John",
    getPerson() {
        console.log(this.name)
    }
}

const student = person.getPerson
student() // output: ??

Explanation

Remember! when comes to regular function, this value depends on HOW function is called. Lets break it down:

  1. this is non-strict mode

  2. there is person object with getPerson method

  3. declare variable student, then assign person.getPerson to it. Here, getPerson is detached from person object. Make it standalone

     const student = person.getPerson
    
  4. student() is called. Pay attention, student is called as standalone function → pure function call.

  5. because its a pure function call, this refer to global object — recall when person.getPerson is detached from person

  6. this try to find name in global context

  7. name is not declared yet in global context → then print undefined

Output:

undefined

Exercise #2

const person = {
  name: "Morgan",
  greet: function () {
    console.log("1:", this.name);

    function inner() {
      console.log("2:", this.name);
    }

    const arrow = () => {
      console.log("3:", this.name);
    };

    inner();
    arrow();
  },
};

person.greet();

Explanation

lets break down the code:

const person = {
  name: "Morgan",
  greet: function () {
    console.log("1:", this.name); // this refer to person

    function inner() {
      console.log("2:", this.name); // this refer to global object
    }

    const arrow = () => {
      console.log("3:", this.name); // this refer to this from greet method. 
    };

    inner();
    arrow();
  },
};
  1. this is non-strict mode

  2. inner() is regular function → this depend on how the function is called.

  3. arrow() is arrow function → this lexically inherited from greet, which is bound to person object

Output:

Morgan
undefined
Morgan

Exercise #3

const person = {
    name: "John",
    getPerson() {
        console.log(this.name)
    }
}

const student = person.getPerson

function getName() {
  this.name = "Frank";
  student();
}

getName(); // output: ??

Explanation

  1. this is non-strict mode

  2. getName is called

  3. in getName function:

    • this.name = “Frank” is set. this in regular function refer to global context, so name property in global context will be Frank

    • student is called

  4. when student is called, same as exercise #1, pay attention to student variable. getPerson binding is detached from its person object. Now its just standalone function

  5. in getPerson function → console.log(this.name), this trying to find name. normally, this will refer to person. But just recall that this method call is detached from its object, this will refer to global object

  6. we know recently we name property in global object to Frank, so this.name in getPerson refer to Frank

Ouput:

Frank
2
Subscribe to my newsletter

Read articles from Aligar Syahan Putra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aligar Syahan Putra
Aligar Syahan Putra