What Exactly is 'this' in JavaScript?

If you've ever scratched your head wondering why this is undefined, or why it suddenly points to the window object when you expected it to be your class instance, you're not alone. The this keyword is one of the most misunderstood concepts in JavaScript.
In this post, we'll demystify this once and for all.
The Golden Rule
Here's the key insight that will save you hours of debugging:
thisis determined by HOW a function is called, not WHERE it's defined.
This is fundamentally different from languages like Java or C++ where this always refers to the current object instance. In JavaScript, this is dynamic and depends on the call site.
The Four Rules of this
JavaScript determines the value of this using four rules, applied in order of precedence:
1. Default Binding (Standalone Function Call)
When a function is called without any context, this defaults to:
undefinedin strict mode- The global object (
windowin browsers,globalin Node.js) in non-strict mode
function showThis() {
console.log(this);
}
showThis(); // undefined (strict mode) or window (non-strict)This is often the source of bugs:
const user = {
name: "Vikram",
greet() {
console.log(`Hello, ${this.name}`);
}
};
const greetFn = user.greet;
greetFn(); // "Hello, undefined" — `this` is not the user object!When we assigned user.greet to a variable and called it standalone, we lost the implicit binding.
2. Implicit Binding (Method Call)
When a function is called as a method of an object, this refers to that object.
const user = {
name: "Vikram",
greet() {
console.log(`Hello, ${this.name}`);
}
};
user.greet(); // "Hello, Vikram" ✅The rule is simple: look at what's left of the dot at the call site.
const user = {
name: "Vikram",
address: {
city: "Hyderabad",
getCity() {
console.log(this.city);
}
}
};
user.address.getCity(); // "Hyderabad" — `this` is `user.address`, not `user`3. Explicit Binding (call, apply, bind)
You can explicitly set this using these methods:
function greet(greeting) {
console.log(`${greeting}, ${this.name}`);
}
const user = { name: "Vikram" };
// call — pass arguments individually
greet.call(user, "Hello"); // "Hello, Vikram"
// apply — pass arguments as an array
greet.apply(user, ["Hi"]); // "Hi, Vikram"
// bind — returns a new function with `this` permanently bound
const boundGreet = greet.bind(user);
boundGreet("Hey"); // "Hey, Vikram"bind is particularly useful for event handlers and callbacks:
class Button {
constructor(label) {
this.label = label;
}
click() {
console.log(`${this.label} clicked`);
}
}
const btn = new Button("Submit");
document.querySelector("button").addEventListener("click", btn.click.bind(btn));4. new Binding (Constructor Call)
When a function is called with new, JavaScript:
- Creates a new empty object
- Sets
thisto that new object - Executes the constructor
- Returns the object (unless the constructor returns something else)
function User(name) {
this.name = name;
}
const vikram = new User("Vikram");
console.log(vikram.name); // "Vikram"With ES6 classes:
class User {
constructor(name) {
this.name = name; // `this` is the new instance
}
}Arrow Functions: A Different Beast
Arrow functions don't have their own this. Instead, they lexically inherit this from the enclosing scope at the time they're defined.
const user = {
name: "Vikram",
greet: () => {
console.log(`Hello, ${this.name}`);
}
};
user.greet(); // "Hello, undefined" — arrow function uses outer `this`!This might seem like a limitation, but it's actually incredibly useful for callbacks:
const user = {
name: "Vikram",
friends: ["Alice", "Bob"],
// ❌ Regular function loses `this`
showFriendsBroken() {
this.friends.forEach(function(friend) {
console.log(`${this.name} knows ${friend}`); // `this` is undefined!
});
},
// ✅ Arrow function preserves `this`
showFriends() {
this.friends.forEach((friend) => {
console.log(`${this.name} knows ${friend}`); // Works!
});
}
};
user.showFriends();
// "Vikram knows Alice"
// "Vikram knows Bob"Arrow Functions Cannot Be Rebound
You cannot change an arrow function's this with call, apply, or bind:
const greet = () => {
console.log(this);
};
const user = { name: "Vikram" };
greet.call(user); // Still the outer `this`, not `user`
greet.apply(user); // Same
greet.bind(user)(); // SameMethod Borrowing
Method borrowing is a technique where you use a method from one object on another object using call or apply. This is possible because this is set at call time.
Borrowing Array Methods
Array-like objects (like arguments, NodeLists, or strings) don't have array methods. You can borrow them:
function sum() {
// `arguments` is array-like but not an array
const args = Array.prototype.slice.call(arguments);
return args.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10Modern alternative using spread:
function sum(...args) {
return args.reduce((a, b) => a + b, 0);
}Borrowing Methods Between Objects
const person = {
name: "Vikram",
greet() {
console.log(`Hello, I'm ${this.name}`);
}
};
const dog = {
name: "Buddy"
};
// Borrow the greet method for dog
person.greet.call(dog); // "Hello, I'm Buddy"Practical Example: Using Math Methods on Arrays
const numbers = [5, 2, 9, 1, 7];
// Math.max expects individual arguments, not an array
Math.max(5, 2, 9, 1, 7); // 9
// Borrow using apply (passes array as individual args)
Math.max.apply(null, numbers); // 9
// Modern alternative: spread operator
Math.max(...numbers); // 9Common Gotchas and How to Fix Them
Gotcha 1: Losing this in Callbacks
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// ❌ `this` will be undefined inside the callback
setInterval(function() {
this.seconds++; // TypeError: Cannot read property 'seconds' of undefined
console.log(this.seconds);
}, 1000);
}
}Solutions:
// Solution 1: Arrow function
start() {
setInterval(() => {
this.seconds++;
console.log(this.seconds);
}, 1000);
}
// Solution 2: Bind the callback
start() {
setInterval(function() {
this.seconds++;
console.log(this.seconds);
}.bind(this), 1000);
}
// Solution 3: Store `this` in a variable (old-school)
start() {
const self = this;
setInterval(function() {
self.seconds++;
console.log(self.seconds);
}, 1000);
}Gotcha 2: Event Handlers in Classes
class Toggle {
constructor(element) {
this.isOn = false;
this.element = element;
// ❌ `this` will be the DOM element, not the Toggle instance
element.addEventListener("click", this.toggle);
}
toggle() {
this.isOn = !this.isOn; // `this` is the <button>, not Toggle!
console.log(this.isOn);
}
}Solution: Use arrow functions or bind
class Toggle {
constructor(element) {
this.isOn = false;
element.addEventListener("click", () => this.toggle());
// OR: element.addEventListener("click", this.toggle.bind(this));
}
toggle() {
this.isOn = !this.isOn;
console.log(this.isOn);
}
}Or use class field arrow functions:
class Toggle {
isOn = false;
// Arrow function as class field — `this` is always the instance
toggle = () => {
this.isOn = !this.isOn;
console.log(this.isOn);
}
constructor(element) {
element.addEventListener("click", this.toggle);
}
}Gotcha 3: Destructuring Methods
const user = {
name: "Vikram",
greet() {
console.log(`Hello, ${this.name}`);
}
};
const { greet } = user;
greet(); // "Hello, undefined" — destructuring loses the binding!Solution: Don't destructure methods you intend to call, or bind them:
const greet = user.greet.bind(user);
greet(); // "Hello, Vikram"Gotcha 4: this in Nested Functions
const user = {
name: "Vikram",
outer() {
console.log(this.name); // "Vikram" ✅
function inner() {
console.log(this.name); // undefined ❌
}
inner();
}
};
user.outer();The inner function is a standalone call, so it gets default binding.
Solution: Arrow function
outer() {
console.log(this.name);
const inner = () => {
console.log(this.name); // "Vikram" ✅
};
inner();
}Interview Questions
Interview Question: What is the
thiskeyword in JavaScript?Answer:
thisis a special keyword that refers to the object that is executing the current function. Unlike other languages wherethisalways refers to the instance, in JavaScriptthisis determined by how a function is called, not where it's defined. Its value depends on the call site and can be the global object, an instance, an explicitly bound object, or undefined.
Interview Question: What is the difference between
call,apply, andbind?Answer: All three set
thisexplicitly.callinvokes the function immediately with arguments passed individually:fn.call(obj, arg1, arg2).applyinvokes immediately with arguments as an array:fn.apply(obj, [arg1, arg2]).binddoes NOT invoke the function - it returns a new function withthispermanently bound:const boundFn = fn.bind(obj).
Interview Question: How is
thisdifferent in arrow functions vs regular functions?Answer: Arrow functions don't have their own
this. They inheritthislexically from the enclosing scope at the time they're defined. Regular functions determinethisat call time based on how they're called. You cannot rebind an arrow function'sthisusingcall,apply, orbind.
Interview Question: What happens to
thiswhen you assign a method to a variable and call it?Answer: You lose implicit binding. When you assign
obj.methodto a variable and call it standalone,thisbecomesundefined(strict mode) or the global object. To preservethis, usebind:const fn = obj.method.bind(obj).
Interview Question: How do you fix
thisissues in class event handlers?Answer: Three solutions:
- Use arrow function wrapper:
element.addEventListener('click', () => this.handleClick())- Use
bind:element.addEventListener('click', this.handleClick.bind(this))- Define the method as a class field arrow function:
handleClick = () => { ... }which automatically bindsthisto the instance
Interview Question: What is method borrowing in JavaScript?
Answer: Method borrowing is using a method from one object on another object using
callorapply. For example, array-like objects (likeargumentsor NodeLists) don't have array methods, but you can borrow them:Array.prototype.slice.call(arguments)or[].forEach.call(nodeList, fn). This works because you're explicitly settingthisto the target object.
Quick Reference Table
| Call Style | this Value |
|---|---|
func() | undefined (strict) or global |
obj.func() | obj |
func.call(obj) | obj |
func.apply(obj) | obj |
func.bind(obj)() | obj |
new Func() | The new instance |
| Arrow function | Lexical (inherited from enclosing scope) |
Summary
- Default binding: Standalone calls get
undefined(strict) or global - Implicit binding: Method calls bind
thisto the object before the dot - Explicit binding:
call,apply,bindlet you setthismanually newbinding: Constructor calls setthisto the new instance- Arrow functions: Don't have their own
this-they inherit from the enclosing scope
The key takeaway? Always think about how a function is called, not where it's written. When in doubt, use arrow functions for callbacks and bind for event handlers.
Loading comments...