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.

9 min read

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.

javascriptjavascript
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.

How JavaScript determines the value of this

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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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 ignored

Once 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.

javascriptjavascript
const calculator = {
  value: 0,
  add(n) {
    this.value += n;
    return this;
  }
};
 
calculator.add(5).add(3);
console.log(calculator.value); // 8

The 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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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.

javascriptjavascript
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 second

Without 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.

javascriptjavascript
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.

javascriptjavascript
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 undefined

Arrow functions defined as class fields sidestep that problem, because each instance gets its own copy already bound to itself.

javascriptjavascript
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 extraction

That 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.

javascriptjavascript
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 calledthis 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 functionthis 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

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.
RunePowered by Rune AI

Frequently Asked Questions

Why does this become undefined in my callback?

When you pass a method as a callback (like obj.method to addEventListener), the method is called without an object context, so this becomes undefined in strict mode (or the global object in sloppy mode). Fix it by using an arrow function wrapper, .bind(), or an arrow function as the method definition.

Does arrow function this work differently in classes?

Yes. An arrow function defined as a class field captures this from the constructor context, which is the instance being created. This means arrow functions in class fields are automatically bound to the instance, making them safe to pass as callbacks.

Can I change this inside an arrow function with call or apply?

No. Arrow functions ignore the first argument to call, apply, and bind. Their this is permanently locked to whatever this was in the scope where the arrow was created.

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.