← back to section

this is one of the most common sources of unexpected bugs in JavaScript. In most languages, this is simply a reference to the current object. In JavaScript it's different: the value of this is determined not by where a function is written, but by how it is called. The same function will get a different this depending on how it is invoked.

Four rules for determining this

1. Method call on an object. this is the object to the left of the dot.

const user = {
  name: "Alice",
  greet() {
    console.log(this.name); // "Alice"
  },
};
user.greet();

2. Plain function call. this is the global object (window in a browser) or undefined in strict mode.

function greet() {
  console.log(this); // window or undefined
}
greet();

3. call, apply, bind. Explicit this assignment.

function greet(greeting) {
  console.log(`${greeting}, ${this.name}`);
}
const user = { name: "Alice" };

greet.call(user, "Hello");      // "Hello, Alice"
greet.apply(user, ["Hello"]);   // same, arguments as array
const bound = greet.bind(user);
bound("Hello");                 // "Hello, Alice"

4. Constructor (new). this is the newly created object.

function User(name) {
  this.name = name;
}
const alice = new User("Alice");
console.log(alice.name); // "Alice"

Strict mode: this without an object

In non-strict mode, a plain function call gives this === window. In strict mode ("use strict") it gives this === undefined. ES modules and class bodies always run in strict mode.

"use strict";
function greet() {
  console.log(this); // undefined, not window
}
greet();

Arrow functions: lexical this

Arrow functions do not have their own this. They capture it from the surrounding lexical context — from the place where the arrow function is written, not from where it is called.

const counter = {
  count: 0,
  start() {
    setInterval(() => {
      this.count++; // this = counter, captured from start()
      console.log(this.count);
    }, 1000);
  },
};
counter.start();

If you replace the arrow with a regular function, this inside setInterval becomes window (or undefined), and count is lost.

Losing this when passing a method as a callback

This is a classic trap. Passing a method as a callback "detaches" it from its object.

const user = {
  name: "Alice",
  greet() {
    console.log(this.name);
  },
};

setTimeout(user.greet, 100); // undefined — this is lost
document.querySelector("button").addEventListener("click", user.greet); // undefined

The method is passed as a bare function and called without an object on the left — rule 2 applies, not rule 1.

Ways to fix it:

// bind — creates a new function with a fixed this
setTimeout(user.greet.bind(user), 100);

// arrow wrapper — this is taken lexically
setTimeout(() => user.greet(), 100);

this in classes

In classes, methods follow the object method rule, but when passed as a callback — the same trap applies. The fix: arrow class fields.

class Button {
  label = "Click me";

  // Regular method — this is lost when passed as a callback
  handleClick() {
    console.log(this.label); // can be undefined
  }

  // Arrow field — this is permanently bound
  handleClickArrow = () => {
    console.log(this.label); // always "Click me"
  };
}

this in React components

In class components, losing this in event handlers was a daily problem: you had to call this.handleClick = this.handleClick.bind(this) in the constructor, or use arrow class fields.

In functional components there is no this problem at all. Event handlers are written as closures — variables are taken from the function's scope, not via this.

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

This is one of the reasons functional components became the standard.

Traps: what does the code print

const obj = {
  val: 42,
  getVal() { return this.val; },
};

const fn = obj.getVal;
console.log(fn());          // undefined (this = window/undefined)
console.log(obj.getVal());  // 42
console.log(fn.call(obj));  // 42
const obj = {
  val: 1,
  outer() {
    const inner = function () {
      return this.val; // this is lost — regular function
    };
    return inner();
  },
};
console.log(obj.outer()); // undefined — use an arrow instead of function to fix

In short

  • this is determined by the way the function is called, not where it is declared.
  • Four rules: method (obj.fn()) → the object; plain call → window/undefined; call/apply/bind → the given object; new → the new object.
  • In strict mode (modules, classes), a plain call without an object gives undefined, not window.
  • Arrow functions have no this of their own — they take it lexically from the surrounding code.
  • Passing a method as a callback loses this. Fix with .bind() or an arrow wrapper.
  • In functional React components this is not needed: handlers work as closures.
  • JavaScript essentials for TypeScript — closures and scope, the foundation for understanding this.
  • Event loop: how it works and where it bites — setTimeout, callbacks, and execution order.
  • DOM events: bubbling, capturing, delegation — where losing this most commonly happens in practice.