Skip to the chapter
←JavaScriptGGroundworkJSJavaScript
JavaScript/Beginner/B12
12 / 41 written chapters
/

The cover
  1. B1Setup & mental model
  2. B2Execution context
  3. B3var, let & const
  4. B4Scope
  5. B5Single thread
  6. B6In the browser
  7. B7Types & values
  8. B8Strings & Math
  9. B9Operators & flow
  10. B10Functions
  11. B11Objects & arrays
  12. B12this
  13. B13Closures
  14. B14DOM & events
  15. B15Callbacks & promises
  16. B16Errors & tools
  17. B17Guided project
  18. ★Cheat page
  1. I1Scope & functions, properly
  2. I2Objects deeply
  3. I3Prototypes & OOP
  4. I4Async, properly
  5. I5Modules & tooling
  6. I6Regex & dates
  7. I7Error handling
  8. I8Real-time connections
  9. I9Offline & storage
  10. I10Browser APIs
  11. I11Modern JS
  12. ★Cheat page
  1. A1Engine & memory
  2. A2Advanced async
  3. A3Metaprogramming
  4. A4Types & data
  5. A5Patterns & architecture
  6. A6Performance
  7. A7Security
  8. A8Ecosystem
  9. A9Testing
  10. A10Observability
  11. A11Interview bank
  12. A12Build it yourself
  13. ★Cheat page

JSB12 · Part 1 · Beginner

The this keyword

Not where it was written — who called it.

6 min read8 sections2 exercises
12 of 41

In this chapter

  • this is an extra parameter you never declared
  • One function, six call sites
  • The four rules, ranked
  • Arrow functions opt out entirely
  • How this gets lost — and the three fixes

and 3 more

about 6 min
08On this page
  1. this is an extra parameter you never declared
  2. One function, six call sites
  3. The four rules, ranked
  4. Arrow functions opt out entirely
  5. How this gets lost — and the three fixes
  6. this in a class
  7. this in an event handler
  8. The traps, collected

this is an extra parameter you never declared

Every ordinary function call quietly passes one more argument than you wrote. You never name it, you never see it in the parameter list, and inside the body it is called this.

What goes in it is not decided when you write the function. It is decided at the moment of the call, by the shape of the call itself. That single sentence dissolves most this confusion, because it means the same function can be handed a different this on every line that calls it.

function whoAmI() {
  return this;
}

const a = { name: "a", whoAmI };
const b = { name: "b", whoAmI };

a.whoAmI();        // this is a
b.whoAmI();        // this is b — same function, different answer
whoAmI();          // this is undefined (strict) — nobody to be
Say it like this → "Reading a function tells you what it does. Only the call site tells you what this is."

One function, six call sites

The same describe function, called six different ways. Step through and watch which rule fires and what this lands on.

The call site decides
Which rule fires
this becomes
Result

Dry run: the same describe() function, six call sites

CallRule that firesthis becomesResult
user.describe()3 — implicit bindinguser"ana"
bare() (describe pulled off user first)4 — default bindingundefinedTypeError: cannot read name of undefined
describe.call({ name: "called" })2 — explicit binding{ name: "called" }"called"
describe.bind(user)()2 — explicit bindinguser, permanently"ana"
new Tag()1 — new binding (wins)the brand-new object"new"
setTimeout(user.describe, 0)4 — default bindingundefinedTypeError again

Same function, five different outcomes — nothing about describe's own code ever changes. The two default-binding rows are the same bug wearing two different outfits: both strip the dot off before the call happens, one by assignment, one by passing the method as a callback.

The four rules, ranked

Four rules can set this, and when more than one could apply, the higher-ranked one wins. Work down the list and stop at the first match.

Rank Rule The call looks like this becomes
1 — wins new binding new Fn() the brand-new object being built
2 Explicit binding fn.call(obj), .apply(obj), .bind(obj) exactly the object you handed over
3 Implicit binding obj.method() the object left of the dot
4 — default Default binding a plain fn() undefined in strict mode and modules; the global object in old sloppy scripts

Only the last dot counts

With implicit binding it is easy to assume the whole path matters. It does not — only the object immediately left of the final dot does.

const app = {
  name: "app",
  inner: {
    name: "inner",
    who() { return this.name; },
  },
};

app.inner.who();     // "inner" — not "app"

call, apply and bind

function greet(greeting, mark) {
  return greeting + ", " + this.name + mark;
}
const user = { name: "ana" };

greet.call(user, "Hi", "!");      // "Hi, ana!"  — arguments listed
greet.apply(user, ["Hi", "!"]);   // "Hi, ana!"  — arguments in an array

const hi = greet.bind(user, "Hi");
hi("!");                          // "Hi, ana!"  — returns a new function instead of calling

The mnemonic writes itself: call takes commas, apply takes an array — and bind is the odd one out because it does not run anything, it hands you a new function with this permanently attached. Binding an already-bound function a second time does nothing; the first weld holds.

Arrow functions opt out entirely

An arrow function has no this of its own. None of the four rules apply to it. When you write this inside an arrow, it is resolved like any other variable — by looking outwards through the scopes it was written in, exactly the way the scope chain works.

const timer = {
  label: "timer",

  withFunction() {
    [1].forEach(function () {
      console.log("function callback:", this === undefined ? "undefined" : this.label);
    });
  },

  withArrow() {
    [1].forEach(() => {
      console.log("arrow callback:  ", this.label);
    });
  },
};

timer.withFunction();
timer.withArrow();

The plain function callback gets its own this by rule 4 — nobody called it with a dot, so it is undefined. The arrow has none to get, so it keeps reading the this belonging to withArrow, which is timer. This is the entire reason arrows took over callbacks.

⚠ Never use an arrow as an object method const o = { name: "o", who: () => this.name }; looks tidier and is broken. The arrow was written at the top level, not inside any function, so its this is whatever the module or script has — never o. Methods want who() { ... }.

How this gets lost — and the three fixes

Almost every real this bug is the same move: the function gets separated from its dot. Three ways it happens, all identical underneath.

const counter = {
  count: 0,
  increment() { this.count++; return this.count; },
};

// 1. Pulled out into a variable
const inc = counter.increment;
inc();                              // 💥 this is undefined

// 2. Passed as a callback
button.addEventListener("click", counter.increment);   // 💥
setTimeout(counter.increment, 100);                    // 💥

// 3. Destructured out
const { increment } = counter;
increment();                        // 💥 same thing, shorter spelling

The fixes, in the order you should reach for them:

// A. Wrap it in an arrow — the call keeps its dot
setTimeout(() => counter.increment(), 100);

// B. Bind it, when you need a reusable detached function
const inc = counter.increment.bind(counter);

// C. In a class, make it a field holding an arrow — bound at construction
class Counter {
  count = 0;
  increment = () => { this.count++; };   // safe to pass anywhere
}

Option A is usually right, because it keeps the object and the method together at the call site where a reader can see them. Option C costs one function per instance rather than one per class, which only matters when you are making thousands of them.

this in a class

class Timer {
  constructor(label) {
    this.label = label;        // rule 1 — this is the new instance
    this.ticks = 0;
  }

  tick() {                     // rule 3 when called as timer.tick()
    this.ticks++;
  }
}

const t = new Timer("mine");
t.tick();                      // fine — there is a dot
[1, 2, 3].forEach(t.tick);     // 💥 no dot. Classes are strict, so this is undefined

Class bodies are always in strict mode, which means a lost this inside a class throws rather than silently writing properties onto the global object. That is a feature: the old sloppy-mode behaviour turned this same bug into a mysterious global variable instead of an error.

this in an event handler

button.addEventListener("click", function () {
  this;                  // the element the listener is attached to
  this.classList.add("on");
});

button.addEventListener("click", () => {
  this;                  // NOT the button — whatever the surrounding scope had
});
Rule In a handler, prefer event.currentTarget over this. It means the same element, it keeps working when you switch to an arrow, and it says out loud what it is. Note currentTarget is the element you attached to; event.target is whatever was actually clicked, which may be a child.

The traps, collected

  • Passing a method as a callback — onClick={obj.method}, setTimeout(obj.method), arr.map(obj.method). The dot does not travel.
  • An arrow as an object method — it reads this from the file, not the object.
  • A plain function callback inside a method — it gets default binding. Use an arrow, or the second argument of forEach/map, which sets this for you.
  • Assuming the whole path binds — only the last dot counts.
  • Expecting a re-bind to work — the first one is permanent.
Say it like this → "this is set by the call, not the definition. Check for new, then for call/apply/bind, then for a dot, and if none of those apply it is undefined. Arrows have no this at all — they borrow the one around them."
Interview answer, one sentence

"this is not where a function was written but how it was called: new, explicit bind/call/apply, a method call and a plain call decide it in that order of precedence, while arrow functions have no this of their own and inherit it."

Before you move on
  • Rank the four binding rules from memory and say which one wins when more than one could apply.
  • Explain why const fn = obj.method; fn(); loses this, and name the fix you'd reach for first.
  • Say why an arrow function should never be used as an object method, in terms of where it looks up this.
  • Trace this through .call, .apply, and .bind on the same function, and say what makes .bind different from the other two.
  • Explain why event.currentTarget is a safer choice than this inside an event handler.

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 8 testsBorrow a method using only the dotbeginner
  • 8 testsA method that survives being detachedbeginner
All 35 for this level →
B12

Finished reading?

0 of 41 chapters read in this series

→Next stop

  1. B11Previous · B11Objects & arrays
  2. B12you are here
  3. Next · B13ClosuresThe function that walked out with its birthplace still attached.8 min read

01On this page

8 sections · 0%

  1. 01this is an extra parameter you never declared
  2. 02One function, six call sites
  3. 03The four rules, ranked
  4. 04Arrow functions opt out entirely
  5. 05How this gets lost — and the three fixes
  6. 06this in a class
  7. 07this in an event handler
  8. 08The traps, collected

[] chapters · t top