JavaScript

this, and the four ways it gets set

Call-site rules, arrow functions, and the callback that loses its this.

After this lesson you can

  • Predict what this refers to from how a function was called
  • Explain why an arrow function has no this of its own
  • Fix a method passed as a callback that has lost its this

this is not fixed to where a function is defined — it is set by how the function is called, freshly, every time.

const user = {
  name: "Nino",
  greet() { return `Hi, ${this.name}`; },
};

user.greet();               // "Hi, Nino" — called as user.greet()
const g = user.greet;
g();                        // "Hi, undefined" — called with no receiver

greet is the exact same function in both calls. What changed is the call site: user.greet() sets this to user; g() sets this to undefined (in strict mode, which modules are by default).

The four rules, in the order that matters

  1. new Fn() — this is the newly created object.
  2. fn.call(obj) / fn.apply(obj) / fn.bind(obj) — this is whatever you explicitly hand it.
  3. obj.method() — this is obj, the thing left of the dot.
  4. A plain call, fn() — this is undefined in strict mode (globalThis otherwise, which is almost never what anyone wants).

Try it

The same method, called two different waysnode-22
const counter = {  count: 0,  increment() {    this.count += 1;    return this.count;  },}; export function run() {  const results = [];  results.push(counter.increment());       // called on counter  const loose = counter.increment;  try {    loose();                                // plain call, no receiver    results.push("no error");  } catch (e) {    results.push(e.constructor.name);  }  return results;}
What to look for

Arrow functions do not have their own this

An arrow function does not set this at all — it reads this from the scope it was written in, the same way it reads any other outer variable, and none of the four rules above apply to it.

const timer = {
  label: "countdown",
  start() {
    setTimeout(function () {
      console.log(this.label);   // undefined — plain call, rule 4
    }, 0);
    setTimeout(() => {
      console.log(this.label);   // "countdown" — reads this from start()
    }, 0);
  },
};

This is the entire reason arrow functions exist for callbacks: a regular function passed to setTimeout, an event listener, or .then() is invoked as a plain call, rule 4, and loses whatever this the method had. An arrow function passed the same way keeps it, because it never had a this of its own to lose.

Fixing a method passed as a callback

class Button {
  constructor(label) { this.label = label; }
  onClick() { console.log(`clicked ${this.label}`); }
}

const b = new Button("Save");
el.addEventListener("click", b.onClick);          // this is lost — rule 4
el.addEventListener("click", () => b.onClick());  // fixed — arrow reads b
el.addEventListener("click", b.onClick.bind(b));   // fixed — this is pinned

Both fixes work. .bind creates a new function permanently locked to that this; the arrow wrapper is usually shorter and just as correct.

Try it yourself

2 visible tests · 2 hidden tests

greeter.greetAll(names) should return one greeting per name, each in the form `${this.prefix}, ${name}`, using this.prefix — never a hard-coded string. Implement greetAll so that mapping a callback over names does not lose this the way a plain function callback easily could.

  • run(["Nino","Ana"])
  • run([])
Loading editor…

Sign up to check the hidden tests and save your progress. Sign up