this, call/apply/bind, and the arrow-function trap
this is the most context-dependent word in JavaScript. Move the same function from one object to another and this changes. Wrap it in an arrow and this freezes. Pass it to setTimeout and this evaporates.
Most developers learn this by trial and error. Seniors learn the four binding rules and then never get surprised again. That is what this lesson does.
The one rule that beats every other rule
this is decided by how a function is called, not where it was written. Same function, different call site, different this. Hold that thought.
There are four call patterns. JS checks them in this order.
newbinding.new Foo()creates a new object and bindsthisto it.- Explicit binding.
fn.call(obj),fn.apply(obj), orfn.bind(obj)setthistoobj. - Implicit binding.
obj.fn()setsthistoobj. - Default binding. Plain
fn()setsthistoglobalThisin sloppy mode, orundefinedin strict mode and ES modules.
The implicit binding trap
const rider = {
name: "Ankit",
greet() {
console.log("Hi", this.name);
},
};
rider.greet(); // Hi Ankit
const grab = rider.greet;
grab(); // Hi undefined (strict) or Hi '' (sloppy)Step-by-step.
- Step 1.
rider.greet()is the implicit pattern.thisbecomesrider. Prints "Hi Ankit". - Step 2.
const grab = rider.greetcopies the function reference, not the binding. Nowgrabis just a plain function. - Step 3.
grab()is the default pattern.thisisundefinedin strict mode.this.namethrows or printsundefined.
Interview trap: "this is lost" almost always means the call site changed. Look at the dot, or the absence of it, right before the parentheses.
Arrow functions, the lexical this
Arrow functions do not have their own this. They reach up to the surrounding lexical scope and use that one. Permanently. You cannot override it with call, apply, bind, or even new.
This is the single biggest difference between function and =>. It is not about syntax. It is about identity.
const order = {
items: ["dosa", "filter coffee"],
total: 0,
charge() {
this.items.forEach(function (item) {
this.total += 100; // this is undefined, breaks
});
},
};The regular function passed to forEach gets its own this, which defaults to undefined. Swap it for an arrow.
charge() {
this.items.forEach((item) => {
this.total += 100; // arrow inherits this from charge, which is order
});
}Now this is order. The arrow did not get its own. It borrowed charge's.
Interview trap: never use an arrow function as an object method.
const obj = { greet: () => this.name }does NOT bindthistoobj. It binds to the module or global scope.
call, apply, bind
Three ways to set this manually. Same job, different ergonomics.
function transfer(amount, currency) {
console.log(`${this.name} sent ${currency}${amount}`);
}
const user = { name: "Priya" };
transfer.call(user, 500, "INR "); // pass args one by one
transfer.apply(user, [500, "INR "]); // pass args as array
const send = transfer.bind(user, 500); // returns new function, no call yet
send("INR "); // INR 500call(thisArg, a, b). Invokes immediately with positional args.apply(thisArg, [a, b]). Invokes immediately with an array of args. Useful when args are already in an array.bind(thisArg, a). Returns a new function withthisand optional args permanently set. Does not invoke.
Mnemonic: C for Comma, A for Array, B for Bind-and-wait.
const upi = {
bank: "HDFC",
send(amount) {
return `${this.bank} debit ₹${amount}`;
},
};
const debitFromICICI = upi.send.bind({ bank: "ICICI" });
debitFromICICI(1499); // "ICICI debit ₹1499"bind is how React class components used to fix the "this in handler" problem before arrow methods became standard.
Quick reference
- Implicit binding
- this is set by the object on the left of the dot at call time.
- Explicit binding
- this is set by call, apply, or bind.
- Lexical this
- Arrow functions do not get their own this. They use the enclosing scope's this.
- bind
- Returns a new function with this and optionally some arguments pre-set.
Predict the output
const counter = {
n: 0,
tickRegular: function () { setTimeout(function () { this.n++; }, 0); },
tickArrow: function () { setTimeout(() => { this.n++; }, 0); },
};
counter.tickRegular();
counter.tickArrow();
console.log(counter.n);Step by step.
- Step 1.
tickRegularschedules a regular function. When it fires,thisisundefined(strict) and then++throws or mutates the wrong object.counter.nstays0. - Step 2.
tickArrowschedules an arrow. It inheritsthisfromtickArrow, which was called ascounter.tickArrow(). Sothisiscounter, andcounter.nbecomes1. - Step 3.
console.log(counter.n)runs synchronously, before either timer fires. Prints0. After the timers run,counter.nis1.
Senior rule: arrow functions are not "shorter functions". They permanently inherit this from their birth scope. Use them for callbacks where you want to keep outer this. Never use them as methods or constructors.
Quick quiz, prove you got it
- 1.In `const fn = obj.method; fn();` (non-strict), what is `this` inside the call?
- 2.What does `func.bind(obj)` return?
- 3.Inside an arrow function, what is `this`?
Interview insights
When asked "explain this in JS"
- this depends on HOW the function is called, not where it is defined
- The four binding rules: default, implicit, explicit (call/apply/bind), new
- Arrow functions are the exception: lexical this
- A real trap example, like assigning a method to a variable and losing this
Tricky questions they will ask
Q.What does `arr.forEach(function() { console.log(this); })` print?›
A.In non-strict mode, the global object. In strict mode, undefined. forEach does not bind this for the callback unless you pass a thisArg as the second argument.
Q.Can you bind this on an arrow function?›
A.No. bind/call/apply have no effect on arrow functions because arrows do not have their own this in the first place. The lexical this wins.
Free tools you can use while you learn
Common questions
Q.What is the difference between call, apply, and bind?›
Q.Why do arrow functions not have their own this?›
Watching quietly. Tap me if you want a tip.
Try this (0 of 2 done)
- 1
Use .bind to fix the lost this, so it logs "Hello, Aarav".
show answer
const customer = { name: 'Aarav', greet() { console.log('Hello, ' + this.name); } }; const greet = customer.greet.bind(customer); greet(); - 2
Use .call to greet on behalf of a different customer named "Ishita".
show answer
const customer = { name: 'Aarav', greet() { console.log('Hello, ' + this.name); } }; customer.greet.call({ name: 'Ishita' });