Skip to content
JavaScript js functions 5 min read

The this Keyword

In JavaScript, this is one of the most misunderstood features because its value is not fixed when a function is written — it is decided when the function is called. The same function can see four completely different this values depending on how it is invoked. Once you internalize that this is bound by the call site (not the definition site), the rules become predictable. This page walks through the four binding rules, how arrow functions opt out of them, and the classic bugs that bite everyone.

How this is decided

When a function runs, JavaScript resolves this by checking the call site against four rules, in order of precedence (highest first):

  1. new binding — the function was called with new.
  2. Explicit binding — the function was called via call, apply, or bind.
  3. Implicit binding — the function was called as a method of an object.
  4. Default binding — none of the above; this falls back to the global object (globalThis) or undefined in strict mode.

Arrow functions sit outside this system entirely: they capture this lexically from their surrounding scope.

Default binding

A plain function call — no object, no new, no bind — uses the default binding. In strict mode (which ES modules and class bodies enable automatically), this is undefined. In sloppy mode it points at the global object.

'use strict';

function whoAmI() {
  return this;
}

console.log(whoAmI()); // undefined (strict mode)

Output:

undefined

In non-strict scripts the same call would log the global object (window in browsers, globalThis in Node). Always prefer strict mode so accidental global this becomes a loud undefined error instead of a silent bug.

Implicit binding

When a function is called as a property of an object — obj.method()this is the object to the left of the dot. The binding comes from the call expression, not from where the method lives.

const user = {
  name: 'Ada',
  greet() {
    return `Hi, I'm ${this.name}`;
  },
};

console.log(user.greet()); // this === user

Output:

Hi, I'm Ada

The danger is that implicit binding is fragile. The moment you detach the method from its object, the dot is gone and so is the binding:

const greet = user.greet; // detached reference
console.log(greet()); // TypeError: Cannot read properties of undefined

Explicit binding

You can force this to a specific value with call, apply, or bind. call and apply invoke the function immediately (differing only in how arguments are passed); bind returns a new function with this permanently locked.

function introduce(greeting) {
  return `${greeting}, I'm ${this.name}`;
}

const dev = { name: 'Grace' };

console.log(introduce.call(dev, 'Hello'));      // args listed
console.log(introduce.apply(dev, ['Hey']));     // args in an array

const boundIntro = introduce.bind(dev);
console.log(boundIntro('Welcome'));             // this is fixed to dev

Output:

Hello, I'm Grace
Hey, I'm Grace
Welcome, I'm Grace

A bound function keeps its this even if later passed to another object or called as a method — bind always wins over implicit binding. See call, apply & bind for the full treatment.

new binding

Calling a function with new creates a brand-new object and sets this to that object for the duration of the constructor. The new object is returned automatically (unless the constructor explicitly returns a different object).

function Point(x, y) {
  this.x = x;
  this.y = y;
}

const p = new Point(3, 4);
console.log(p); // { x: 3, y: 4 }

Output:

Point { x: 3, y: 4 }

new binding has the highest precedence — even a function that was previously bind-ed will use the fresh object when called with new.

Arrow functions and lexical this

Arrow functions do not have their own this. They inherit it from the enclosing lexical scope at the time they are defined, and that binding can never be changed — call, apply, bind, and even new have no effect on an arrow’s this. This makes them ideal for callbacks that need to “see” the surrounding object.

const timer = {
  label: 'tick',
  start() {
    setTimeout(() => {
      console.log(this.label); // arrow inherits start()'s this
    }, 0);
  },
};

timer.start();

Output:

tick

The lost this bug

The most common this bug is passing a method as a callback. Once it is called by the host (event system, setTimeout, array iterator), it is no longer called with the dot, so implicit binding is lost.

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

// BROKEN: setInterval calls increment() with no object
setInterval(counter.increment, 1000); // this is undefined → TypeError

// FIXED: lock this with bind...
setInterval(counter.increment.bind(counter), 1000);

// ...or wrap in an arrow that preserves the call site
setInterval(() => counter.increment(), 1000);

Binding rules at a glance

Call formRulethis value
fn()Defaultundefined (strict) / global (sloppy)
obj.fn()Implicitobj
fn.call(o) / fn.apply(o)Explicito
fn.bind(o)()Explicito (locked)
new Fn()newthe newly created object
() => {}Lexicalinherited from enclosing scope

Best Practices

  • Treat this as a property of the call, not the function — always trace how a function is invoked, not where it was written.
  • Use arrow functions for callbacks inside methods so they inherit the right this automatically.
  • Reach for bind when you must pass a method as a standalone callback (e.g. to an event listener or scheduler).
  • Keep code in strict mode (modules and classes do this for you) so a missing this fails loudly as undefined.
  • Don’t use arrow functions as object methods that rely on this — they capture the outer scope, not the object.
  • Avoid relying on default binding; it almost always signals an accidental detachment.
Last updated June 1, 2026
Was this helpful?