The JavaScript this Keyword Full Deep Dive
The this keyword trips up more JavaScript developers than almost anything else. Learn exactly how this is determined at call time, the binding rules, and how to never guess wrong again.
The this keyword refers to the execution context of a function. For a regular function, its value is not determined by where the function is written, but by how it is called at runtime.
function showThis() {
console.log(this.name);
}
const objA = { name: "A", showThis };
const objB = { name: "B", showThis };
objA.showThis(); // "A"
objB.showThis(); // "B"The exact same function produces a different result depending on which object it is called on. The value before the dot becomes this: that is runtime binding, and it is the single idea this whole article builds on.
How JavaScript Picks a Value
JavaScript checks the call site against a small set of rules, in order of precedence, to decide what this should be.
Arrow functions sit outside the call-time rules entirely because their this is fixed when they are defined, not when they are called. Every other case depends on the call site: whether new was used, whether call, apply, or bind set it explicitly, whether the function was called as a method, and finally the strict-mode default.
new Binding
When a function is called with new, JavaScript creates a brand new empty object and sets this to that object.
function Person(name) {
this.name = name;
}
const p = new Person("Alice");
console.log(p.name); // "Alice"The new keyword overrides every other binding rule. Even if the function has a bind applied to it, new still creates a fresh object rather than reusing the bound one.
Explicit Binding: call, apply, bind
The call, apply, and bind methods let you pass this as an explicit argument instead of relying on the call site.
function greet(greeting) {
return `${greeting}, I am ${this.name}`;
}
const user = { name: "Alice" };
console.log(greet.call(user, "Hello")); // "Hello, I am Alice"
console.log(greet.apply(user, ["Hi"])); // "Hi, I am Alice"
const boundGreet = greet.bind(user);
console.log(boundGreet("Hey")); // "Hey, I am Alice"Call and apply invoke the function immediately with a given this; the only difference is that call takes arguments individually and apply takes them as an array. Bind instead returns a new function with this permanently locked in.
function readValue() {
return this.value;
}
const boundOnce = readValue.bind({ value: 42 });
const boundTwice = boundOnce.bind({ value: 99 });
console.log(boundOnce()); // 42
console.log(boundTwice()); // 42, the second bind is ignoredOnce a function is bound, calling bind again on it has no effect. The first bound this wins for the lifetime of that function.
Implicit Binding: Method Calls
When a function is called as a method of an object, this is that object.
const calculator = {
value: 0,
add(n) {
this.value += n;
return this;
}
};
calculator.add(5).add(3);
console.log(calculator.value); // 8The object before the dot becomes this inside add, and that holds true even when the method is inherited through the prototype chain rather than defined directly on the object.
The Losing-this Trap
The most common bug with implicit binding happens when a method is extracted from its object.
const user = {
name: "Alice",
greet() {
return `Hi, ${this.name}`;
}
};
const greet = user.greet; // Extracted, no longer attached to user
console.log(greet()); // "Hi, undefined"Calling greet() on its own is a standalone call with no object before the dot, so this falls back to the strict-mode default of undefined. Reading a missing property off that gives undefined rather than "Alice". The fix is to bind the function or wrap it in an arrow function before passing it around.
const safeGreet = user.greet.bind(user);
console.log(safeGreet()); // "Hi, Alice"Lexical this: Arrow Functions
Arrow functions do not have their own this. They capture this from the surrounding scope at the moment they are defined.
const obj = {
name: "Outer",
regularMethod() {
const arrowFn = () => console.log("Arrow:", this.name);
arrowFn();
}
};
obj.regularMethod(); // "Arrow: Outer"Inside regularMethod, this is obj, and the arrow function captures that same this regardless of how or where it is later called. This makes arrow functions a safe choice for callbacks.
class Timer {
constructor() {
this.seconds = 0;
}
start() {
setInterval(() => {
this.seconds += 1;
console.log(this.seconds);
}, 1000);
}
}
const timer = new Timer();
timer.start(); // logs 1, then 2, then 3, once per secondWithout the arrow function, this inside the setInterval callback would be undefined instead of the Timer instance, because a plain callback loses its connection to the object it came from.
Default Binding: Standalone Calls
When none of the other rules apply, this is undefined in strict mode and the global object otherwise.
function sloppy() {
console.log(this); // globalThis in non-strict code
}
function strictFn() {
"use strict";
console.log(this); // undefined
}
sloppy();
strictFn();Class bodies and ES modules are always strict mode by default, so you never need to add a strict mode pragma inside a class.
this in Classes
Class methods follow the same implicit binding rule as plain objects, but classes are always strict, so an unbound method call produces undefined rather than the global object.
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count += 1;
return this.count;
}
}
const c = new Counter();
console.log(c.increment()); // 1
const detached = c.increment;
// detached(); throws: Cannot read properties of undefinedArrow functions defined as class fields sidestep that problem, because each instance gets its own copy already bound to itself.
class Ticker {
count = 0;
increment = () => {
this.count += 1;
return this.count;
};
}
const t = new Ticker();
const detached2 = t.increment;
console.log(detached2()); // 1, still works after extractionThat convenience uses more memory than a shared prototype method, since every instance stores its own copy of the function. Reach for it specifically when you need to pass the method as a callback.
this in Event Handlers
In DOM event handlers added with addEventListener, this is the element that fired the event, but only for a regular function handler.
const button = document.querySelector("button");
button.addEventListener("click", function () {
this.textContent = "Clicked"; // "this" is the button element
});An arrow function handler does not receive the element as this; it captures the outer scope's this instead. Use event.currentTarget when you need the element and a lexical this at the same time.
Quick Reference
| How the function is called | this value (strict mode) |
|---|---|
obj.method() | obj |
func() | undefined |
new Func() | new empty object |
func.call(ctx) or func.apply(ctx) | ctx |
func.bind(ctx)() | ctx, permanently |
| Arrow function | this from enclosing scope |
| DOM event handler (regular function) | the element that fired the event |
Mastering this is about tracing the call site rather than the function definition. Check for an arrow function first, then look for new, call, apply, or bind, then check for an object before the dot, and fall back to undefined only when nothing else applies.
For more on how this interacts with prototypes, see the article on constructor functions and bind vs call vs apply.
Rune AI
Key Insights
- The value of this is determined at call time, not definition time, for regular functions.
- In a method call (obj.method()), this is the object before the dot.
- In a plain function call, this is undefined in strict mode and the global object otherwise.
- Arrow functions do not have their own this. They capture this from the enclosing scope.
- call, apply, and bind set this explicitly. bind creates a permanently bound function.
- Class bodies are always in strict mode, so unbound method calls produce undefined, not the global object.
Frequently Asked Questions
Why does this become undefined in my callback?
Does arrow function this work differently in classes?
Can I change this inside an arrow function with call or apply?
Conclusion
The value of this is not determined by where a function is defined, but by how it is called. In a method call, this is the object before the dot. In a plain function call, this is undefined (strict) or the global object. Arrow functions ignore call-site binding and capture this from their surrounding scope. call, apply, and bind let you set this explicitly. Once you internalize these binding rules, this stops being mysterious and becomes predictable.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.