this
Refers to the context of the current call, determined by how the function was invoked.
this- Added in JavaScript ECMAScript 1 (1997)
Return value
anyThe current execution context: the receiver of a method call, the new object in a constructor, globalThis or undefined in a plain call, or the enclosing scope's this inside an arrow function.
Errors
In strict mode a plain function call leaves this as undefined, so reading a property off it throws a TypeError; a derived class constructor throws a ReferenceError if this is touched before super().
Changelog
- PHP ECMAScript 5 (2009)Strict mode removed this substitution: a plain call leaves this as undefined instead of replacing it with the global object, and primitives passed via call/apply are no longer boxed.
- PHP ECMAScript 2015Arrow functions have no own this, so it resolves lexically and cannot be changed by call, apply or bind. At the top level of a module this is undefined rather than globalThis.
this is not decided by where a function is written but by how it is called. Reading the call site answers almost every question about it:
| Call form | this is |
|---|---|
obj.fn() | the object before the dot |
fn() | undefined in strict code and modules, globalThis in sloppy code |
new Fn() | the newly constructed object |
fn.call(o) / fn.apply(o) | o (boxed if primitive, in sloppy code) |
| arrow function | whatever this was where the arrow was written |
| class body | the instance in methods, the class itself in statics |
What trips people up
Detaching a method loses the receiver. const f = obj.method; f() is a plain call — the dot is gone, so this is undefined. This is why passing a method as a callback throws cannot read properties of undefined. Fix it with obj.method.bind(obj), an arrow wrapper () => obj.method(), or a class-field arrow.
Class bodies and modules are always strict, so there is no fallback to globalThis to mask the mistake there.
Example
When to use it
- Diagnose why a method throws once it is passed as a callback
- Share one helper across several objects by choosing the receiver at the call site
- Write a class method that reads and updates its own instance state
- Decide between an arrow and a regular function for a callback inside a method
Frequently asked questions
Why is this undefined inside my function?
Because the function was called plainly, with nothing before a dot, in strict code — class bodies and ES modules are always strict, so there is no fallback to globalThis. Bind the receiver with .bind(obj), wrap the call in an arrow, or call it as obj.method().
How do I keep this when passing a method as a callback?
Use obj.method.bind(obj), wrap it as () => obj.method(), or define the handler as a class-field arrow function. All three fix the receiver so that the eventual plain call still sees the right object.
Does this work differently in arrow functions?
Yes — arrow functions have no this of their own, so it resolves lexically to the scope where the arrow was written and never changes. That makes arrows ideal for callbacks inside a method and wrong for object-literal methods, prototype methods and DOM handlers.
More examples
The call site decides, not the definition
One single function object gives three different answers depending on how it is reached: through a, through b, or bare. bind() is the exception that freezes the answer — call() on the bound copy cannot override it, so the last line still prints "pinned".
function whoami() {
return this?.id ?? "no this";
}
const a = { id: "a", whoami };
const b = { id: "b", whoami };
console.log(a.whoami(), b.whoami(), whoami());
console.log(whoami.call({ id: "explicit" }));
const pinned = whoami.bind({ id: "pinned" });
console.log(pinned.call({ id: "ignored" }));Arrow fields versus prototype methods
Inside list() the arrow passed to map keeps the method's this, so no bind or self alias is needed. In the class, the arrow field survives being pulled off the instance while the prototype method throws — the trade-off is that the arrow is allocated per instance instead of shared.
const shop = {
name: "shop",
items: ["a", "b"],
list() { return this.items.map((i) => `${this.name}/${i}`); },
};
console.log(shop.list());
class Button {
label = "OK";
handleArrow = () => this.label; // captured at construction
handleMethod() { return this.label; } // needs a receiver
}
const btn = new Button();
const h1 = btn.handleArrow, h2 = btn.handleMethod;
console.log(h1());
try { h2(); } catch (e) { console.log("method detached:", e.constructor.name); }
Discussion