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.
"use strict";
const user = {
name: "Nino",
greet() { return `Hi, ${this.name}`; },
};
user.greet(); // "Hi, Nino" — called as user.greet()
const g = user.greet;
g(); // TypeError — 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, so reading this.name throws. That is strict-mode
behaviour, and ES modules and class bodies are always strict. In old
non-strict script code a plain call gets globalThis instead, and g()
would quietly read globalThis.name ("Hi, undefined" in Node) —
which is worse, because nothing fails. The sandbox runs examples as
non-strict CommonJS-style code, so they opt in with "use strict" on
the first line.
The four rules, in the order that matters
new Fn()—thisis the newly created object.fn.call(obj)/fn.apply(obj)/fn.bind(obj)—thisis whatever you explicitly hand it.obj.method()—thisisobj, the thing left of the dot.- A plain call,
fn()—thisisundefinedin strict mode (globalThisotherwise, which is almost never what anyone wants).
Try it
"use strict"; 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.name}: ${e.message}`); } return results;}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 — setTimeout chose this
}, 0);
setTimeout(() => {
console.log(this.label); // "countdown" — reads this from start()
}, 0);
},
};
A regular function passed as a callback gets whatever this the code
calling it decides on: .then() calls its callbacks with undefined
(which non-strict code turns into globalThis), Node's
setTimeout passes its own Timeout object, a DOM listener gets the
element. It is never the method's this. An arrow function passed the
same way keeps the method's this, because it never had one of its own
to replace. The flip side: an arrow function used as a method does
not see the object either.
"use strict"; export function run() { const cart = { items: ["pen", "lamp"], regular() { return this === cart; }, arrow: () => this === cart, countLater() { return [1].map(() => this.items.length)[0]; }, }; return { regularSeesCart: cart.regular(), arrowSeesCart: cart.arrow(), arrowCallbackInMethod: cart.countLater(), };}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 the element, not b
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.
Lesson 8 looks at call, apply and bind in detail.
Common mistakes
- Passing
obj.methodas a callback. The function arrives without its object; in strict codethisisundefinedand the firstthis.xthrows aTypeError, as the first example shows. - Writing an object method as an arrow function.
{ total: () => this.items.length }readsthisfrom outside the object literal, so it never sees the object. - A regular
functioninside a method.items.forEach(function () { this.count++ })is a plain call; is not the object. Use an arrow function.
Exercise · Pro
A method that keeps its this inside a callback
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade