Skip to content

This Keyword in JavaScript

You’ve learned how to create objects, write methods on them, and pass functions around as values. Inside those methods, you’ve probably used this to reach the object’s own properties.

But here’s something odd. The same function can give different results depending on how you call it:

const user = {
    name: "Manmeet",
    greet() {
        console.log(`Hi, I'm ${this.name}`);
    },
};

user.greet(); // Hi, I'm Manmeet

const greet = user.greet;
greet(); // Hi, I'm undefined

Same function, same code. So why did this.name change?

The answer is that this isn’t decided when a function is written. It’s decided when the function is called. This article shows you the rules JavaScript follows to work out what this points to, and how you can control it.

Think about the word “I”. On its own, it doesn’t refer to anyone in particular. Its meaning depends entirely on who is speaking.

When Alice says “I’m hungry”, “I” means Alice. When Bob says the exact same sentence, “I” means Bob. The sentence never changed, only the speaker did.

this works the same way. A function that uses this is like a sentence that uses “I”. The code inside stays the same, but what this refers to depends on who is calling the function at that moment.

In the example above, user.greet() is user saying the sentence, so this is user. But greet() on its own has no speaker in front of it, so this doesn’t point to user anymore.

this is a special keyword in JavaScript that refers to the object a function is being called on.

The important word here is called. JavaScript doesn’t care where the function was written. It only cares about how the function was called at that moment.

Hmm, sounds a bit abstract, right? Let’s look at the rules JavaScript follows, one by one, and it’ll start to click.

Let’s start with the case you’ve already seen:

const user = {
    name: "Riya",
    greet() {
        console.log(`Hi, I'm ${this.name}`);
    },
};

user.greet(); // Hi, I'm Riya

In the above example, you can see that:

  1. greet is a method stored inside the user object.
  2. We call it as user.greet(), with user before the dot.
  3. So user is the speaker, this is user, and this.name is "Riya".

Simple enough. Now what if the same function is shared between two objects?

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

const riya = { name: "Riya", introduce };
const aman = { name: "Aman", introduce };

riya.introduce(); // Hi, I'm Riya
aman.introduce(); // Hi, I'm Aman

In the above example, you can see that:

  1. introduce is written only once, outside of both objects.
  2. Both riya and aman store the same function under the key introduce.
  3. When riya calls it, this is riya. When aman calls it, this is aman.

Same sentence, different speaker. Just like our “I” analogy. Cool!

Now you might be wondering: what if there’s nothing before the dot at all? Who’s the speaker then?

"use strict";

function whoAmI() {
    console.log(this);
}

whoAmI(); // undefined

In the above example, you can see that:

  1. whoAmI is called on its own, with no object in front of it.
  2. There’s no speaker, so in strict mode, this is simply undefined.
  3. In non-strict mode, JavaScript falls back to the global object instead (window in the browser, globalThis in Node).

Now let’s go back to the example we started this article with:

const greet = user.greet;
greet(); // Hi, I'm undefined

In the above example, you can see that:

  1. const greet = user.greet doesn’t copy the object. It only copies the function.
  2. greet() is then called on its own, with nothing before the dot.
  3. So this isn’t user anymore. In non-strict mode, it’s the global object, which knows nothing about our user. In strict mode, it’s undefined, and this.name throws TypeError: Cannot read properties of undefined (reading 'name').

Makes sense? The function didn’t change at all. Only the way we called it did.

Okay, so far so good. But this is where most people actually get bitten by this. Have a look:

const user = {
    name: "Riya",
    greet() {
        console.log(`Hi, I'm ${this.name}`);
    },
};

setTimeout(user.greet, 1000); // Hi, I'm undefined

Wait, what? We clearly wrote user.greet. So where did Riya go?

In the above example, you can see that:

  1. We passed user.greet to setTimeout. Remember from the Higher Order Functions article, we pass the function itself, not the result of calling it. So only the function goes in, not the user object.
  2. After 1 second, setTimeout calls that function on its own, like greet().
  3. There’s no object before the dot, so this is not user, and our name is lost.

It’s the exact same problem as const greet = user.greet, just hidden inside setTimeout.

We’ll fix this in a bit. But first, let’s talk about arrow functions, because they play by completely different rules.

Let’s write greet as an arrow function and see what happens:

const user = {
    name: "Riya",
    greet: () => {
        console.log(`Hi, I'm ${this.name}`);
    },
};

user.greet(); // Hi, I'm undefined

Hmm, that’s strange. We called it as user.greet() with user right before the dot, and it still didn’t work.

In the above example, you can see that:

  1. Arrow functions don’t have their own this.
  2. Instead, they use the this from the place where they were written, just like a closure remembers variables from where it was created.
  3. Our arrow function was written outside of any method, so it uses the this from outside the object, which is not user. Calling it with user.greet() doesn’t change that.

So arrow functions are a bad choice for object methods. But here’s the twist: that same behaviour makes them really useful inside methods.

const user = {
    name: "Riya",
    greetLater() {
        setTimeout(() => {
            console.log(`Hi, I'm ${this.name}`);
        }, 1000);
    },
};

user.greetLater(); // Hi, I'm Riya (after 1 second)

In the above example, you can see that:

  1. greetLater is a normal method, so when we call user.greetLater(), its this is user.
  2. The arrow function inside doesn’t have its own this, so it borrows the this of greetLater, which is user.
  3. Even though setTimeout calls the arrow function on its own a second later, this is still user.

If we had used a normal function () {} as the callback here, this would have been lost again, just like in rule 3.

There’s one more way a function can be called, and that’s with the new keyword:

function User(name) {
    this.name = name;
}

const riya = new User("Riya");
console.log(riya.name); // Riya

In the above example, you can see that:

  1. new User("Riya") makes JavaScript create a brand new empty object.
  2. Inside User, this is that new object, so this.name = name adds a name property to it.
  3. At the end, new returns the object, which we store in riya.

The same thing happens inside a class’s constructor.

Cool! Now you know how JavaScript picks this on its own. But now you might be wondering: can I just tell JavaScript what this should be, instead of leaving it to how the function is called?

Yes, you can! And that’s where call, apply and bind come in.

Controlling this with call, apply and bind

Section titled “Controlling this with call, apply and bind”

Every function in JavaScript comes with three built-in methods: call, apply and bind. All three let you choose what this should be.

Going back to our analogy, these let you hand the sentence to a specific person and say: “Here, you say it.”

Let’s start with call:

function introduce(city, hobby) {
    console.log(`Hi, I'm ${this.name} from ${city}, and I love ${hobby}`);
}

const riya = { name: "Riya" };
const aman = { name: "Aman" };

introduce.call(riya, "Srinagar", "painting");
// Hi, I'm Riya from Srinagar, and I love painting

introduce.call(aman, "Jammu", "cricket");
// Hi, I'm Aman from Jammu, and I love cricket

In the above example, you can see that:

  1. introduce is a normal function. It isn’t stored inside riya or aman at all.
  2. introduce.call(riya, ...) runs introduce immediately, with this set to riya.
  3. The rest of the arguments, "Srinagar" and "painting", are passed one by one to city and hobby.

Notice that we never had to put introduce inside the objects. call lets any object borrow the function. Pretty neat, right?

Now let’s do the same thing with apply:

introduce.apply(riya, ["Srinagar", "painting"]);
// Hi, I'm Riya from Srinagar, and I love painting

In the above example, you can see that:

  1. apply also runs introduce immediately, with this set to riya.
  2. The only difference is that the arguments are passed as an array, instead of one by one.

That’s it. That’s the whole difference. An easy way to remember it:

  • call → commas
  • apply → array

Hmm, you may be wondering why you’d ever need apply then. It’s handy when your arguments are already sitting in an array:

const details = ["Jammu", "cricket"];
introduce.apply(aman, details);
// Hi, I'm Aman from Jammu, and I love cricket

In the above example, you can see that we already had details as an array, so we passed it straight to apply without pulling the values out one by one.

Now for my favourite one, bind. Remember the example we started this article with? Let’s fix it:

const user = {
    name: "Riya",
    greet() {
        console.log(`Hi, I'm ${this.name}`);
    },
};

const greet = user.greet.bind(user);
greet(); // Hi, I'm Riya

In the above example, you can see that:

  1. bind doesn’t run the function. Instead, it returns a new function.
  2. In that new function, this is permanently locked to the object we passed, which is user.
  3. So even when we call greet() on its own, this is still user.

Cool! Now remember our setTimeout problem from rule 3? bind fixes that too:

setTimeout(user.greet.bind(user), 1000); // Hi, I'm Riya (after 1 second)

In the above example, you can see that:

  1. user.greet.bind(user) creates a new function with this locked to user.
  2. We pass that new function to setTimeout.
  3. When setTimeout calls it on its own a second later, this is still user.

Makes sense? You can also pass arguments to bind, and they get locked in too:

function multiply(a, b) {
    return a * b;
}

const double = multiply.bind(null, 2);
console.log(double(5)); // 10

In the above example, you can see that:

  1. We don’t care about this here, so we pass null.
  2. a is locked to 2, so double is a new function that only needs b.
  3. When we call double(5), 5 goes to b, and we get 10.

Looks familiar? It’s the same double we built with closures in the last article, just made with bind this time.

Phew, that was a lot! Let’s put all three side by side:

Method Runs the function? How arguments are passed Returns
call Yes, immediately One by one, with commas The function’s result
apply Yes, immediately As a single array The function’s result
bind No One by one, with commas A new function to call later

Use call or apply when you want to run a function right now with a specific this. Use bind when you’re handing the function to someone else to run later, like a callback or an event listener.

Still feeling a bit confused? Don’t worry, it happens to everyone. Whenever you see this, just ask these questions in order:

  1. Is it inside an arrow function? Then this is whatever it is in the code around the arrow function.
  2. Was the function called with new? Then this is the new object being created.
  3. Was it called with call, apply or bind? Then this is the object you passed.
  4. Was it called with an object before the dot? Then this is that object.
  5. None of the above? Then this is undefined in strict mode, or the global object otherwise.

Now you know why the same function can print a different name depending on how you call it.

You learned how to:

  • Explain what this is and why it’s decided when a function is called
  • Work out this for methods, plain function calls, callbacks, arrow functions and new
  • Use call and apply to run a function with a specific this
  • Use bind to lock this for functions that run later

Whenever you’re unsure, don’t look at where the function was written. Look at how it’s called. this is just the “I” in the sentence, and it means whoever is speaking.