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.
Analogy
Section titled “Analogy”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.
What is this?
Section titled “What is this?”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.
How JavaScript Decides What this Is
Section titled “How JavaScript Decides What this Is”1. Calling a Function as a Method
Section titled “1. Calling a Function as a Method”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:
greetis a method stored inside theuserobject.- We call it as
user.greet(), withuserbefore the dot. - So
useris the speaker,thisisuser, andthis.nameis"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:
introduceis written only once, outside of both objects.- Both
riyaandamanstore the same function under the keyintroduce. - When
riyacalls it,thisisriya. Whenamancalls it,thisisaman.
Same sentence, different speaker. Just like our “I” analogy. Cool!
2. Calling a Function on Its Own
Section titled “2. Calling a Function on Its Own”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:
whoAmIis called on its own, with no object in front of it.- There’s no speaker, so in strict mode,
thisis simplyundefined. - In non-strict mode, JavaScript falls back to the global object instead (
windowin the browser,globalThisin 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:
const greet = user.greetdoesn’t copy the object. It only copies the function.greet()is then called on its own, with nothing before the dot.- So
thisisn’tuseranymore. In non-strict mode, it’s the global object, which knows nothing about our user. In strict mode, it’sundefined, andthis.namethrowsTypeError: Cannot read properties of undefined (reading 'name').
Makes sense? The function didn’t change at all. Only the way we called it did.
3. Losing this in Callbacks
Section titled “3. Losing this in Callbacks”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:
- We passed
user.greettosetTimeout. 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 theuserobject. - After 1 second,
setTimeoutcalls that function on its own, likegreet(). - There’s no object before the dot, so
thisis notuser, 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.
4. Arrow Functions
Section titled “4. Arrow Functions”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:
- Arrow functions don’t have their own
this. - Instead, they use the
thisfrom the place where they were written, just like a closure remembers variables from where it was created. - Our arrow function was written outside of any method, so it uses the
thisfrom outside the object, which is notuser. Calling it withuser.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:
greetLateris a normal method, so when we calluser.greetLater(), itsthisisuser.- The arrow function inside doesn’t have its own
this, so it borrows thethisofgreetLater, which isuser. - Even though
setTimeoutcalls the arrow function on its own a second later,thisis stilluser.
If we had used a normal function () {} as the callback here, this would have been lost again, just like in rule 3.
5. Calling a Function with new
Section titled “5. Calling a Function with new”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:
new User("Riya")makes JavaScript create a brand new empty object.- Inside
User,thisis that new object, sothis.name = nameadds anameproperty to it. - At the end,
newreturns the object, which we store inriya.
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:
introduceis a normal function. It isn’t stored insideriyaoramanat all.introduce.call(riya, ...)runsintroduceimmediately, withthisset toriya.- The rest of the arguments,
"Srinagar"and"painting", are passed one by one tocityandhobby.
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:
applyalso runsintroduceimmediately, withthisset toriya.- 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:
binddoesn’t run the function. Instead, it returns a new function.- In that new function,
thisis permanently locked to the object we passed, which isuser. - So even when we call
greet()on its own,thisis stilluser.
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:
user.greet.bind(user)creates a new function withthislocked touser.- We pass that new function to
setTimeout. - When
setTimeoutcalls it on its own a second later,thisis stilluser.
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:
- We don’t care about
thishere, so we passnull. ais locked to2, sodoubleis a new function that only needsb.- When we call
double(5),5goes tob, and we get10.
Looks familiar? It’s the same double we built with closures in the last article, just made with bind this time.
call vs apply vs bind
Section titled “call vs apply vs bind”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.
Quick Way to Find this
Section titled “Quick Way to Find this”Still feeling a bit confused? Don’t worry, it happens to everyone. Whenever you see this, just ask these questions in order:
- Is it inside an arrow function? Then
thisis whatever it is in the code around the arrow function. - Was the function called with
new? Thenthisis the new object being created. - Was it called with
call,applyorbind? Thenthisis the object you passed. - Was it called with an object before the dot? Then
thisis that object. - None of the above? Then
thisisundefinedin strict mode, or the global object otherwise.
🧵 Wrapping It Up
Section titled “🧵 Wrapping It Up”Now you know why the same function can print a different name depending on how you call it.
You learned how to:
- Explain what
thisis and why it’s decided when a function is called - Work out
thisfor methods, plain function calls, callbacks, arrow functions andnew - Use
callandapplyto run a function with a specificthis - Use
bindto lockthisfor 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.