The Magic of this, call(), apply(), and bind() in JavaScript
Learn about this keyword and learn how to control function context like a pro.
If you ask any JavaScript developer what the most confusing part of the language is, there is a very high chance they will say the this keyword.
It behaves differently depending on where you use it, how you call it, and whether or not you are in strict mode. But once you understand the simple mental model behind it, the mystery disappears. And once you master the tools to manipulate it—call(), apply(), and bind()—you unlock a whole new level of flexibility in your code.
Here is your straightforward, jargon-free guide to understanding context in JavaScript.
What does this actually mean?
Instead of diving into deep compiler internals and execution contexts, let’s use a simple golden rule:
this simply refers to "who is calling the function right now."
Imagine a function is a phone call. If you pick up the phone and ask, "Who is this?", the answer depends entirely on who dialed your number.
In JavaScript, this is a dynamic reference to the object that invoked (called) the function.
1. this Inside Objects
When a function is stored inside an object, it is called a method. When you call that method, this points directly to the object that holds it.
const user = {
name: "Alice",
age: 25,
introduce: function() {
// "this" refers to the 'user' object because 'user' called the function
console.log(`Hi, I am \({this.name} and I am \){this.age} years old.`);
}
};
user.introduce();
// Output: Hi, I am Alice and I am 25 years old.
Who is calling the function? The user object. Therefore, this.name translates to user.name.
2. this Inside Normal (Standalone) Functions
When you call a function that is not a property of an object—just a plain function call—this depends on strict mode:
In non-strict mode,
thisfalls back to the global object (in browsers,windoworglobalThis).In strict mode,
thisbecomesundefined.
Examples:
function show() {
console.log(this);
}
show(); // non-strict: window (in browser)
// strict: undefined (if 'use strict' at top)
Why that matters: if you expect this to refer to some object but you call the function without that object as the caller, you get the global object or undefined instead—leading to bugs (e.g., accidentally creating globals, or TypeErrors when accessing properties on undefined).
Common gotcha: extracting a method out of its object:
const user = {
name: 'Ada',
greet() { console.log(this.name); }
};
const greet = user.greet;
greet(); // undefined (strict) or '' (global.name) — you lost the user binding
To preserve the intended this, use explicit binding (next section), call it as a method (user.greet()), or use an arrow function bound in the right scope.
3. this with Constructors and new
When you call a function with new, JavaScript:
Creates a fresh object,
Sets
thisinside the function to that new object,Returns the object (unless the function explicitly returns another object).
Example:
function Person(name) {
this.name = name;
}
const p = new Person('Lin');
console.log(p.name); // "Lin"
Important precedence rule (short): "new" binding takes priority over implicit/explicit binding. If you try to force a this with call/apply on a constructor, new will still create the instance and use it as this instead.
4. this in Arrow Functions (lexical this)
Arrow functions do not have their own this. Instead they inherit this from the surrounding (lexical) scope — like closures for this.
const obj = {
value: 42,
regular() { console.log(this.value); }, // 42
arrow: () => console.log(this.value) // likely undefined or outer scope value
};
obj.regular(); // 42
obj.arrow(); // not 42 — arrow's `this` is set where the function was defined
Use arrow functions when you want to preserve the outer this (commonly used in callbacks), but do NOT use them as object methods or constructors.
5. Explicit Binding: call(), apply(), bind()
These are the explicit tools to set this for a function.
fn.call(thisArg, ...args)— callfnimmediately withthisArgand a list of args.fn.apply(thisArg, argsArray)— callfnimmediately withthisArgand arguments as an array.fn.bind(thisArg, ...boundArgs)— return a NEW function permanently bound tothisArg(and optionally pre-filled args).
Examples and typical use cases:
Borrowing a method:
const obj = { name: 'Ada' };
function say(greeting) {
console.log(greeting + ', ' + this.name);
}
say.call(obj, 'Hello'); // "Hello, Ada"
say.apply(obj, ['Hi']); // "Hi, Ada"
Using apply with arrays (common pre-spread era):
const nums = [3, 1, 4];
Math.max.apply(null, nums); // 4
// Modern alternative: Math.max(...nums)
Partial application with bind:
function multiply(a, b) { return a * b; }
const double = multiply.bind(null, 2);
console.log(double(5)); // 10
Keeping method this when passing as callback:
class Button {
constructor() {
this.count = 0;
this.onClick = this.onClick.bind(this); // ensure `this` inside onClick is the instance
}
onClick() {
this.count++;
console.log(this.count);
}
}
const btn = new Button();
setTimeout(btn.onClick, 100); // Works because bound
Notes:
callvsapply: same effect;applyaccepts arguments as an array.bindreturns a new function; use it when you need a stablethisfor future calls (event handlers, callbacks).Historically,
null/undefinedpassed tocall/applydefault to global object in non-strict mode; in strict mode they remainnull/undefined.
Precedence summary (most to least):
newcall/apply/bind(explicit)method-call (implicit)
global/default
Edge detail: using bind and then new — the bound thisArg is ignored when constructing; the new instance becomes this, but bound arguments remain prepended.
6. Practical Patterns & Real-World Examples
Method borrowing:
- Use
Array.prototype.slice.call(arguments)(older pattern) to convertargumentsto array. Now preferArray.from(arguments)or rest params.
- Use
Event handlers in classes:
Bind handlers in constructor, or use class fields with arrow functions:
handleClick = () => { console.log(this); }
Partial application:
bindis a simple built-in way to pre-fill arguments.
Function utilities:
call/applylet you reuse generic functions on different objects.
7. Common Pitfalls & How to Avoid Them
Losing
thiswhen passing methods as callbacks — usebind, arrow functions, or wrap in another function.Incorrect assumptions about
thisinside arrow functions — remember arrow functions getthislexically.Accidentally relying on global
thisin non-strict mode — always use strict mode or prefer explicit bindings.Overusing
bindfor performance-critical hot paths — binds create new functions each time; cache the bound function if reused.
8. Quick Cheat Sheet
Method call:
obj.fn()→thisisobj.Function call:
fn()→ non-strict: global; strict:undefined.Constructor:
new Fn()→thisis the new instance.Arrow function:
() => {}→thisis inherited from outer scope.Explicit:
fn.call(obj, ...),fn.apply(obj, [...]),fn.bind(obj)→ forcethis.
Conclusion
Understanding this becomes simple if you apply the golden rule: think about "who is calling the function right now" and remember the four primary binding ways (default, implicit, explicit, and new), plus the special lexical behavior of arrow functions. Once you can identify which binding is in effect, call, apply, and bind give you the tools to control or preserve that binding when you need to.

