How to control this in JavaScript without losing your mind.
this in JavaScript without losing your mind.Introduction
Few things confuse JavaScript developers more than this. It behaves differently depending on how a function is called. To make matters worse, we also have three helpers—bind, call, and apply—that let us manually control this.
Good news: these aren’t magic. Once you see the patterns, they’re simple. Think of them as ways to borrow methods or lock in context.
In this guide, we’ll break them down with clear rules, copy-paste snippets, and real-world scenarios.
1. Why Do We Need These?
By default, this is determined by how you call a function, not where it’s defined.
function greet() {
console.log("Hello, I’m " + this.name);
}
const user = { name: "Alice", greet };
user.greet(); // "Hello, I’m Alice"
const fn = user.greet;
fn(); // "Hello, I’m undefined" (or global object in non-strict mode)
Problem: once you detach greet, this gets lost.
Solution: use bind, call, or apply to control this.
2. call: Invoke with a Specific this
Syntax:
func.call(thisArg, arg1, arg2, ...)
- Immediately invokes the function.
- Sets
thistothisArg. - Arguments are passed individually.
Example: Borrowing a method
function greet(greeting) {
console.log(greeting + ", I’m " + this.name);
}
const user = { name: "Alice" };
const admin = { name: "Bob" };
greet.call(user, "Hi"); // "Hi, I’m Alice"
greet.call(admin, "Hey"); // "Hey, I’m Bob"
👉 Think: “Call right now with this context.”
3. apply: Like call, but Args as an Array
Syntax:
func.apply(thisArg, [argsArray])
- Same as
call, but arguments are given as an array (or array-like).
Example: Passing dynamic arguments
function sum(a, b, c) {
return a + b + c;
}
console.log(sum.apply(null, [1, 2, 3])); // 6
Real use: Math.max with an array
const nums = [4, 7, 2];
console.log(Math.max.apply(null, nums)); // 7
👉 Modern alternative: spread syntax
console.log(Math.max(...nums)); // 7
4. bind: Create a New Function with Fixed this
Syntax:
const boundFn = func.bind(thisArg, arg1, arg2, ...)
- Returns a new function with
thispermanently bound tothisArg. - You can also preset arguments (partial application).
- Doesn’t run immediately — you call it later.
Example: Fixing lost context
const user = {
name: "Alice",
greet() {
console.log("Hi, I’m " + this.name);
}
};
setTimeout(user.greet, 1000);
// ❌ "Hi, I’m undefined"
setTimeout(user.greet.bind(user), 1000);
// ✅ "Hi, I’m Alice"
Example: Partial application
function multiply(a, b) {
return a * b;
}
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
👉 Think: “Bind later, call anytime.”
5. Visualizing the Difference

6. Real-World Examples
a) Borrowing Array Methods
const arrayLike = { 0: "a", 1: "b", length: 2 };
const realArray = Array.prototype.slice.call(arrayLike);
console.log(realArray); // ["a", "b"]
b) Event Handlers in React/DOM
class Button {
constructor(label) {
this.label = label;
this.handleClick = this.handleClick.bind(this); // fix `this`
}
handleClick() {
console.log(this.label);
}
}
Without bind, this would be undefined in strict mode event handlers.
c) Variadic Utilities
function logArgs() {
console.log([].join.call(arguments, ", "));
}
logArgs("a", "b", "c"); // "a, b, c"
7. Common Gotchas
7.1 Losing this
const obj = { name: "Alice", greet() { console.log(this.name); } };
const fn = obj.greet;
fn(); // undefined
Fix: fn.bind(obj) or always call obj.greet() directly.
7.2 Overbinding
function say() { console.log(this.x); }
const obj = { x: 10 };
const bound = say.bind(obj);
bound.bind({ x: 20 })(); // still 10
👉 Once bound, this can’t be rebound.
7.3 Arrow Functions Ignore bind
const obj = { name: "Alice" };
const arrow = () => console.log(this.name);
arrow.bind(obj)(); // still undefined
Arrow functions capture this lexically—bind has no effect.
8. Performance Notes
.bindcreates a new function every time—cache it if used in loops or renders..call/.applyoverhead is negligible in most apps, but avoid in hot loops (prefer spread syntax).- Engines optimize common use cases, so choose readability over micro-optimizations.
Conclusion
bind, call, and apply are simply ways to control this and arguments:
- Use
callwhen you want to run immediately with explicit arguments. - Use
applywhen arguments are in an array. - Use
bindwhen you need a reusable function with fixedthis(and maybe preset args).
Pro tip: When debugging this, literally ask: “How is this function being called?” If you don’t like the answer, fix it with one of these three tools.
Call to Action
Which one tripped you up the most when you first learned JS — apply’s array arguments, or arrow functions ignoring bind?
💬 Drop your story in the comments.
🔖 Bookmark this as your this survival guide.
👩💻 Share with a teammate who keeps saying “why is this undefined?”


Leave a Reply