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

live example

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

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

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

live example

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

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

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

live example

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"
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

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

live example

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

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

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.

live example

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

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

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

live example

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
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

live example

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
Run

Running examples is part of paid access. There the same code runs inside the article: editor, run and check next to the paragraph. Three free days →

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.