this in JavaScript: Binding Rules and Arrow Functions

this in JavaScript: Binding Rules and Arrow Functions

JavaScript’s this depends on the call site, with explicit binding and new taking precedence; arrow functions inherit this, making them useful for callbacks but unsuitable as object methods.

Martin Ferret

Martin Ferret

November 10, 2026

One sentence solves most this bugs: the value of this is not decided where a function is written, it is decided where the function is called.

Read that again with a bug in mind. The method is correct. The object is correct. What changed is who called the function, and that is the only thing this looks at.

Rule one: look at the call site

When a function is called as a method, this is the object to the left of the dot.

const counter = {
  count: 0,
  increment() {
    this.count++;
  }
};

counter.increment(); // `this` is counter

Nothing about increment is attached to counter. The binding happens at the moment of the call, and it lives exactly as long as that call.

Which is why this breaks:

const increment = counter.increment;

increment(); // `this` is no longer counter

Same function, no dot, no receiver. This is the single most common this bug, and it usually arrives disguised as a callback:

button.addEventListener('click', counter.increment);

The listener is called by the DOM, not by counter. Inside a listener added with addEventListener, this is the element the listener is attached to, which is also what event.currentTarget gives you.

Rule two: you can set it explicitly

Three methods let you decide the receiver yourself.

increment.call(counter);      // arguments listed after
increment.apply(counter, []); // arguments in an array
const bound = increment.bind(counter); // returns a new function

call and apply invoke immediately and differ only in how they take arguments. bind invokes nothing: it returns a new function whose this is locked forever. Binding it again has no effect, so fn.bind(a).bind(b) is still bound to a.

bind is the fix for the callback above:

button.addEventListener('click', counter.increment.bind(counter));

Rule three: new creates the receiver

Calling a function with new creates a fresh object and binds this to it. That is the whole mechanism behind constructors and classes, and it outranks everything else.

Rule four: the default, and why it throws

With no receiver and no explicit binding, this falls back to the default. In strict mode it is undefined. In sloppy mode it is globalThis.

This is what really happened in the detached increment above. In a module, this is undefined and the call throws a TypeError on the spot. In a plain script, the same call quietly creates a count property on globalThis and increments that instead, which is the worse of the two outcomes.

It matters more than it sounds, because strict mode is not opt in anymore. Modules are always strict, and class bodies are always strict. So a detached class method fails loudly rather than corrupting the global object.

The four rules resolve in this order, highest first:

  1. new
  2. bind, call, apply
  3. Method call, the object before the dot
  4. Default, undefined in strict mode

Arrow functions do not play

An arrow function has no this of its own. It does not participate in the rules above at all. When you write this inside an arrow, the engine looks outward to the enclosing scope, exactly as it would for any other variable.

That is the real definition, and it explains both what arrows fix and what they break.

What they fix: a callback inside a method keeps the method's receiver.

const timer = {
  seconds: 0,
  start() {
    setInterval(() => this.seconds++, 1000); // `this` is timer
  }
};

The arrow inherits this from start, which was called on timer. No bind, no const self = this.

What they break: an arrow used as a method has nothing useful to inherit.

const counter = {
  count: 0,
  increment: () => {
    this.count++; // not the counter
  }
};

There is no enclosing method here, so this comes from the surrounding scope, which in a module is undefined. The rule of thumb writes itself: arrow functions for callbacks, regular functions for methods.

The class field trade-off

You will see this pattern in React and Angular codebases:

class Counter {
  count = 0;
  increment = () => {
    this.count++;
  };
}

As a class field, the arrow is created during construction and captures the instance. The method can be passed anywhere without losing its receiver, which is genuinely convenient.

The cost is that it is no longer a method. It lives on each instance instead of on the prototype, so every instance carries its own copy, and it cannot be overridden through the prototype chain the way a real method can.

Convenient for event handlers, wasteful for everything else.

What to take away

When this surprises you, do not reread the function. Find the call site and ask what is to the left of the dot.

If the answer is nothing, you have three options: call it through its object, bind it, or wrap it in an arrow that inherits the right this. Which one depends on whether you need a reusable function or just this one call.

Ready to prove your JavaScript skills?

Get certified in JavaScript

More JavaScript articles

Keep learning JavaScript. Then get certified when you are ready.