this In Depth
Fundamentals Chapter 5 and 7 both flagged this behaviour without fully resolving it: regular functions get their own this determined by how they're called; arrow functions inherit it from their surrounding scope. This chapter makes that precise, covers the situations where this goes wrong, and introduces three methods — call, apply, bind — that control it directly.
The Real Rule: this Depends on HOW a Function Is Called
The exact same function, called two different ways, gets a different this. person.greet() sets this to person, because it's called through person. Once that same function is assigned to greetFn and called on its own, there's no object to its left at the call site — this falls back to undefined (in strict mode/modules) rather than person.
button.addEventListener("click", person.greet) (Fundamentals Chapter 8) hands the function over to be called plain later — by the time the browser actually calls it, this is no longer person. This is the single most common real-world "this" bug.
call() and apply() — Explicitly Setting this for One Call
call and apply run a function immediately, with the first argument forced into the role of this for that one call — regardless of how the function was originally defined or attached. They're functionally identical except for how the remaining arguments are passed: call lists them one by one, apply takes them as a single array (similar in spirit to the spread operator from Intermediate Chapter 1).
bind() — Permanently Locking this for Later
Unlike call/apply, bind doesn't run the function immediately — it returns a brand-new function with this permanently locked to whatever was passed in, no matter how that new function later gets called. This is the standard fix for the callback problem above: bind the method to its object before handing it off as a callback.
Arrow Functions: No Own this, Inherited Instead
Had the setInterval callback been a regular function instead, this inside it would be undefined (plain-call rule from earlier), breaking this.seconds++ entirely. The arrow function instead has no this of its own — it transparently uses whatever this was already in scope at the point it was written, which is timer, since start() itself was called as timer.start().
function for object methods that need their own this (set by however they're called). Use an arrow function for callbacks nested inside a method, specifically so they inherit the surrounding this instead of losing it.
Why Arrow Functions Can't Be Fixed with bind/call/apply
Since an arrow function never has its own this to begin with, call/apply/bind have nothing to override — they're silently ignored for the this argument specifically. This is the one situation where those three methods simply don't apply.
| Method | Runs immediately? | Sets this for |
|---|---|---|
| fn.call(obj, a, b) | Yes | That one call, args listed individually |
| fn.apply(obj, [a, b]) | Yes | That one call, args as an array |
| fn.bind(obj) | No — returns a new function | Every future call of the returned function |
Coding Challenges
Write a plain function introduce(role) that logs `${this.name} works as a ${role}`. Create two different objects, each with a name property, and use call() to invoke introduce with each object as this, passing a different role string each time.
📄 View solutionCreate an object counter with a count property (0) and a method increment() that increases count by 1 and logs it. Extract increment as a standalone function, use bind() to lock it to counter, then call the bound version three times via setTimeout to prove this stays correct even when called asynchronously.
📄 View solutionCreate an object stopwatch with elapsed: 0 and a method start() that uses setInterval with an ARROW function to increment elapsed and log it every second, for 3 seconds (then stop with clearInterval). Confirm elapsed actually increases by logging it.
📄 View solutionChapter 4 Quick Reference
- this depends on the call site — obj.method() sets this to obj; a plain call doesn't
- fn.call(obj, a, b) — runs fn immediately with this = obj, args listed individually
- fn.apply(obj, [a, b]) — same as call, but args passed as an array
- fn.bind(obj) — returns a NEW function with this permanently locked to obj
- Regular functions get their own this; arrow functions inherit this from their surrounding scope
- call/apply/bind have no effect on an arrow function's this — there's nothing to override
- Passing a method as a bare callback loses its this — bind it first, or use an arrow wrapper
- Next chapter: error handling — try/catch/finally and custom Error classes