The this Keyword
Learn how this works in JavaScript, why it can be confusing, and how to control it with call, apply, and bind.
The this Keyword
this is one of the most confusing things in JavaScript. In most languages this always refers to the current object. In JavaScript — this depends entirely on how and where a function is called.
The same function can have a different this every time it is called. Understanding exactly how this is determined removes all the confusion.
The Core Rule
this is not determined when a function is defined. It is determined when a function is called.
There are four rules that determine what this is — and they apply in this order of priority:
Rule 1 — Default Binding
When a function is called on its own — not as a method, not with new — this is the global object (window in browsers, global in Node.js). In strict mode it is undefined.
function showThis() {
console.log(this);
}
showThis(); // window (browser) or global (Node.js)"use strict";
function showThis() {
console.log(this);
}
showThis(); // undefined — strict modeDefault binding is the fallback when no other rule applies. In modern JavaScript with modules — which always run in strict mode — default binding gives undefined, not the global object.
Rule 2 — Implicit Binding
When a function is called as a method of an object — object.method() — this is that object.
const user = {
name: "Ali",
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
user.greet(); // Hello, I am Ali — this is userThe object to the left of the dot at the time of the call is this.
Implicit binding can be lost
This is the most common this bug. When you take a method out of an object and call it separately, the binding is lost.
const user = {
name: "Ali",
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
const greetFn = user.greet; // copy the function — not the binding
greetFn(); // Hello, I am undefined — this is global or undefinedThe function is the same — but now it is called without an object to the left of the dot. Default binding applies.
// Same issue with callbacks
const user = {
name: "Ali",
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
setTimeout(user.greet, 1000);
// Hello, I am undefined — setTimeout calls greet without the user contextRule 3 — Explicit Binding
Use call(), apply(), or bind() to explicitly set what this should be.
call() — call with explicit this
function greet(greeting, punctuation) {
console.log(`${greeting}, I am ${this.name}${punctuation}`);
}
const ali = { name: "Ali" };
const sara = { name: "Sara" };
greet.call(ali, "Hello", "!"); // Hello, I am Ali!
greet.call(sara, "Hi", "."); // Hi, I am Sara.call(thisArg, arg1, arg2, ...) — first argument is this, rest are the function arguments.
apply() — same as call but arguments as array
greet.apply(ali, ["Hello", "!"]); // Hello, I am Ali!
greet.apply(sara, ["Hi", "."]); // Hi, I am Sara.apply(thisArg, [arg1, arg2, ...]) — arguments passed as an array. Useful when you already have arguments in an array.
const numbers = [5, 3, 8, 1, 9, 2];
// Math.max does not accept an array — apply solves this
console.log(Math.max.apply(null, numbers)); // 9
// Modern equivalent with spread
console.log(Math.max(...numbers)); // 9bind() — create a new function with fixed this
bind() does not call the function immediately. It returns a new function with this permanently bound.
function greet(greeting) {
console.log(`${greeting}, I am ${this.name}`);
}
const ali = { name: "Ali" };
const aliGreet = greet.bind(ali); // new function with this = ali
aliGreet("Hello"); // Hello, I am Ali
aliGreet("Hi"); // Hi, I am Ali — this is always aliFixing lost binding with bind()
const user = {
name: "Ali",
greet() {
console.log(`Hello, I am ${this.name}`);
}
};
// Fix the setTimeout binding problem
setTimeout(user.greet.bind(user), 1000); // Hello, I am Ali ✅Partial application with bind()
bind() can also pre-fill arguments — called partial application:
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2); // a is always 2
const triple = multiply.bind(null, 3); // a is always 3
console.log(double(5)); // 10
console.log(triple(5)); // 15null as the first argument means "I don't care about this" — the function does not use it.
Rule 4 — new Binding
When you call a function with new, a brand new object is created and this refers to it.
function Person(name, age) {
this.name = name; // this = new object
this.age = age;
}
const ali = new Person("Ali", 22);
console.log(ali.name); // Ali
console.log(ali.age); // 22When new is used:
- A new empty object is created
thisis set to that new object- The function runs
- The new object is returned automatically
Arrow Functions and this
Arrow functions do not have their own this. They inherit this from the surrounding scope where they were defined — called lexical this.
const user = {
name: "Ali",
friends: ["Sara", "Zara", "Omar"],
showFriends() {
// 'this' here is user — regular method
this.friends.forEach(friend => {
// Arrow function inherits 'this' from showFriends
console.log(`${this.name} is friends with ${friend}`);
});
}
};
user.showFriends();
// Ali is friends with Sara
// Ali is friends with Zara
// Ali is friends with OmarWithout an arrow function:
showFriends() {
this.friends.forEach(function(friend) {
// Regular function — 'this' is lost here
console.log(`${this.name} is friends with ${friend}`);
// this.name is undefined
});
}Arrow functions fix the this problem inside callbacks naturally — no need for bind() or saving this to a variable.
The old workaround — self or that
Before arrow functions, developers used a workaround:
showFriends() {
const self = this; // save reference to this
this.friends.forEach(function(friend) {
console.log(`${self.name} is friends with ${friend}`); // use saved reference
});
}Arrow functions make this unnecessary.
Arrow functions and object methods
Arrow functions inherit this from where they are defined — not from the object that calls them. This makes them wrong for object methods.
const user = {
name: "Ali",
// ❌ Arrow function — this is not user
greet: () => {
console.log(`Hello, I am ${this.name}`); // undefined
},
// ✅ Regular function — this is user
greet() {
console.log(`Hello, I am ${this.name}`); // Ali
}
};this in Classes
Inside a class method, this refers to the instance — as long as the method is called on the instance.
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// ❌ Regular function loses this
setInterval(function() {
this.seconds++; // this is undefined or global
}, 1000);
}
startFixed() {
// ✅ Arrow function inherits this from startFixed
setInterval(() => {
this.seconds++; // this is the Timer instance
console.log(this.seconds);
}, 1000);
}
}
const timer = new Timer();
timer.startFixed(); // 1, 2, 3...Class field arrow functions — permanently bound methods
class Button {
constructor(label) {
this.label = label;
this.clickCount = 0;
}
// Arrow function as class field — this is permanently bound
handleClick = () => {
this.clickCount++;
console.log(`${this.label} clicked ${this.clickCount} times`);
};
}
const btn = new Button("Like");
// Safe to pass as callback — this is always the instance
document.querySelector("button").addEventListener("click", btn.handleClick);handleClick is defined as an arrow function class field — this is permanently bound to the instance. You can safely pass it as a callback without bind().
this in Event Handlers
In a regular function event handler, this is the element that fired the event.
const btn = document.querySelector("button");
btn.addEventListener("click", function() {
console.log(this); // the button element
this.classList.toggle("active");
});In an arrow function event handler, this is inherited from the outer scope — not the element.
btn.addEventListener("click", () => {
console.log(this); // outer scope this — probably window
// ❌ cannot use this to reference the button
});Use regular functions for event handlers when you need this to be the element. Use arrow functions when you need this to be the surrounding class or object.
Determining this — A Practical Checklist
When you see a function call and wonder what this is — ask these questions in order:
1. Is it called with new?
→ this = new empty object
2. Is it called with call(), apply(), or bind()?
→ this = explicitly provided object
3. Is it called as object.method()?
→ this = object to the left of the dot
4. Is it an arrow function?
→ this = inherited from the surrounding scope
5. None of the above?
→ this = global object (or undefined in strict mode)A Real Example — Event Handler in a Class
class SearchBar {
constructor(inputId, resultsId) {
this.input = document.getElementById(inputId);
this.results = document.getElementById(resultsId);
this.query = "";
// Arrow function — this is always the SearchBar instance
this.input.addEventListener("input", (e) => {
this.query = e.target.value;
this.search();
});
this.input.addEventListener("keydown", (e) => {
if (e.key === "Escape") this.clear();
});
}
async search() {
if (!this.query.trim()) {
this.results.innerHTML = "";
return;
}
try {
const response = await fetch(`/api/search?q=${this.query}`);
const data = await response.json();
this.renderResults(data);
} catch (error) {
console.error("Search failed:", error);
}
}
renderResults(items) {
this.results.innerHTML = items
.map(item => `<li>${item.title}</li>`)
.join("");
}
clear() {
this.query = "";
this.input.value = "";
this.results.innerHTML = "";
}
}
const search = new SearchBar("search-input", "search-results");All event handlers use arrow functions — this always refers to the SearchBar instance. Methods call each other through this. No bind() calls needed anywhere.
Summary
thisis determined at call time — not at definition time- Default binding — standalone function call —
thisis global orundefinedin strict mode - Implicit binding —
object.method()—thisis the object to the left of the dot - Implicit binding can be lost when a method is extracted and called separately
- Explicit binding —
call(),apply(),bind()— setsthisexplicitly call(obj, arg1, arg2)— calls immediately withthisset toobjapply(obj, [args])— same ascallbut arguments as arraybind(obj)— returns a new function withthispermanently set — does not call immediatelynewbinding —new Fn()—thisis the newly created object- Arrow functions do not have their own
this— they inherit it from the surrounding scope - Use arrow functions for callbacks inside methods — use regular functions for methods themselves
- Class field arrow functions —
handleClick = () => {}— permanently bindthisto the instance