If you’ve even been confused by the word “this” in JavaScript, you’re not alone. I’ve struggled with it too, especially when it behaves differently in functions, objects or events. In this comprehensive guide, we’ll break down everything about “this” in JavaScript, starting from the absolute basics.

What is “this”?
In JavaScript, “this” is a special keyword that refers to an object. Think of it as a pronoun in English. Just like we use “he” or “she”, which refers to a person we’re talking about, “this” refers to an object in our code
The tricky part? The object that “this” refers to changes depending on how and where a function is called. This is what makes “this” confusing for beginners.
Why do we need “this”?
Imagine you’re writing code for a video game with many characters. Each character has a name and can introduce themselves:
function introduce() {
console.log("Hi, I am " + this.name);
}
const character1 = {
name: "Mario",
introduce: introduce // reusing the above function...
};
const character2 = {
name: "Luigi",
introduce: introduce // reusing the above function...
};
character1.introduce(); // Hi, I am Mario
character2.introduce(); // Hi, I am Luigi
Without this, we’d have to write the same function repeatedly for each character. With this, the function can figure out which character is calling it and use that character’s name automatically.
The Golden Rule of “this’“
Remember, this refers to the object that is executing the current function. But how do we know which object is executing the function? That depends on how the function is called. Let’s explore each scenario clearly.
Scenario 1: “this” in Regular Functions (Global Context)
When you use “this” in a regular function that’s not part of an object, it refers to the global object. In a browser, the global object is called window. In Node.js, it’s called global.
function showThis() {
console.log(this);
}
showThis(); // In a browser, this will log the Window object...
Note: If you’re using strict mode (by adding “use strict” at the top of your file), this will be undefined instead of the global object. This is actually safer and helps catch errors.
"use script";
function showThis() {
console.log(this);
}
showThis(); // undefined...
Scenario 2: “this” in Object Methods
When a function is part of an object (it is called method) , this refers to the object that owns the method.
const person = {
name: "Priyanshu",
age: 19,
greet: function() {
console.log("Hello, my name is " + this.name);
console.log("I am " + this.age + " years old");
}
};
person.greet();
// Output:
// Hello, my name is Priyanshu
// I am 19 years old
Here, this.name means the name property of the person object and this.age means the age property of the person object.
Think like this way: when you call person.greet(), JavaScript looks at what’s before the dot(person) and makes “this” refer to that object.
Scenario 3: “this” Gets Confusing (Function References)
This is the part that often confuses beginners. Watch what happens when we save a method to a variable:
const person = {
name: "Anonymous",
greet: function() {
console.log("Hello, my name is " + this.name);
}
};
const greetFunction = person.greet;
greetFunction(); // Hello, my name is undefined
Why is the name undefined? It is because when we call greetFunction(), there’s nothing before the dot! JavaScript doesn’t know it came from the person object, so this becomes the global object (or undefined here in strict mode), which doesn’t have a name property.
Scenario 4: “this” in Arrow Functions
Arrow functions, introduced in ES6, handle this completely different. They don’t create their own this. Instead, they inherit this from their surrounding code (we call this lexical scoping).
const person = {
name: "Bob",
regularFunction: function() {
console.log("Regular: ", this.name);
},
arrowFunction: () => {
console.log("Arrow: ", this.name);
}
};
person.regularFunction(); // Regular: Bob
person.arrowFunction(); // Arrow: undefined
The arrow function doesn’t bind this to the object (person). Instead, it captures this from the surrounding scope at the time it was created. Since that surrounding scope is at top level i.e. global this, this.name is undefined (assuming strict mode only).
When Arrow Functions are helpful:
Arrow functions shine when you need to preserve this from an outer function:
const person = {
name: "Bob",
hobbies: ["reading" , "coding" , "cricket"],
showHobbies: function() {
this.hobbies.forEach((hobby) => {
console.log(this.name + "likes " + hobby);
}
}
};
person.showHobbies();
// Output:
// Bob likes reading
// Bob likes coding
// Bob likes cricket
The arrow function inside forEach inherits from this from showHobbies, so it correctly refers to the person object. If we used a regular function, this would be different inside the **forEach **callback because in a regular function, it gets its own **this and **that is undefined.
Scenario 5: “this” in Event Listeners
When you attach an event listener to a DOM element, this inside the handler function refers to that element.
const button = document.querySelector('button');
button.addEventListener("click", function() {
console.log(this); // button element
this.style.backgroundColor = 'violet';
});
However, if you use arrow function, this won’t refer to the button:
button.addEventListener("click", () => {
console.log(this); // NOT the button! It's the global object or undefined...
});
Controlling “this”: The Power Tools
JavaScript gives us 3 methods to explicitly control what this refers to:
- call()
- apply()
- bind()
The call() Method
call() lets you call a function and tell it what this should be:
function greet() {
console.log("Hello, I am " + this.name);
}
const person1 = { name: "Alice" }
const person2 = { name: "Bob" }
greet.call(person1); // Hello, I am Alice
greet.call(person2); // Hello, I am Bob
You can also pass the arguments after this value:
function introduce(greeting, punctuation) {
console.log(greeting + ", I am " + this.name + punctuation);
}
const person = { name: "Alice" }
introduce.call(person , "Hello" , "!"); // Hello, I am Alice!
The apply() Method
apply() is almost identical to call(), but it takes arguments as an array:
function introduce(greeting, punctuation) {
console.log(greeting + ", I am " + this.name + punctuation);
}
const person = { name: "Bob" };
introduce.apply(person, ["Hello", "!"]); // Hello, I am Bob!
So, the difference is: call() takes arguments separately whereas apply() takes them as an array.
The bind() Method
bind() creates a new function with this permanently set to whatever you specify. Unlike call() and apply(), it doesn’t immediately call the function.
const person = {
name: "Bob",
greet: function() {
console.log("Hello, I am " + this.name);
}
};
const greetFunction = person.greet;
greetFunction(); // Hello, I am undefined (this is lost!)
const boundGreetFunction = person.greet.bind(person);
boundGreetFunction(); // Hello, I am Bob (this is preserved!)
This is very much useful for event listeners and callbacks where we want to preserve this:
class Counter = {
constructor() {
this.count = 0;
this.button = document.querySelector('button');
// Without bind, "this" would be the button, not the Counter object...
// So, it would print undefined in that case...
this.button.addEventListener("click", this.increment.bind(this));
}
increment() {
this.count++;
console.log(this.count);
}
}
const counter = new Counter();
// Output: 1, 2, 3 & so on...
Common Pitfalls and How to avoid them
Pitfall 1: Losing “this” in Callbacks
const person = {
name: "Alice",
greet: function() {
console.log("Hello, " + this.name);
}
};
setTimeout(person.greet , 1000); // Hello, undefined
Solution: We can make it correct using 3 approaches: Use bind, an arrow function or a **wrapper **function.
// Solution 1: bind
setTimeout(person.greet.bind(person), 1000);
// Solution 2: arrow function
setTimeout(() => person.greet(), 1000);
// Solution 3: wrapper function
setTimout(function() {
person.greet();
}, 1000);
Pitfall 2: “this” in Nested Functions
const person = {
name: "Bob",
hobbies: ["coding" , "gaming"],
showHobbies: function() {
this.hobbies.forEach(function(hobby) {
console.log(this.name + " likes " + hobby);
});
}
};
Solution: Use arrow functions or save this to a variable:
// Solution 1: Arrow Function
showHobbies: function() {
this.hobbies.forEach((hobby) => {
console.log(this.name + " likes " + hobby);
});
}
// Solution 2: save "this" to a variable (older approach)
showHobbies: function() {
const self = this;
this.hobbies.forEach(function(hobby) {
console.log(self.name + " likes " + hobby);
});
}
Quick Reference: “this” Behavior Summary
- In a regular function (global scope): this = global object (window/global) or undefined in strict mode
- In an object method: this => the object before the dot
- In an arrow function: this => inherited from surrounding code
- In a constructor with new: this => the newly created object
- In an event listener: this => the element that received the event
- With call/apply/bind: this => whatever you specify
Conclusion
Understanding this in JavaScript takes time and practice. The key is remembering that this isn’t determined by where a function is written, but by how it’s called. Each time you see this in code, ask yourself: “How is this function being called?“ The answer will tell you what this refers to.
All the key scenarios are covered. Hope you liked it and remember, this is tricky, but now you’ve got the cheat codes ;)
Comments
Loading comments…