CodeOath
← All posts
JavaScript75 min total · 15 parts

JavaScript Core Concepts: Scope, Closures, `this`, and the Event Loop

Contents — Part 7 of 15: this: How It Gets Bound
Part 7 of 15 · ~2 min

this: How It Gets Bound

Unlike most variables, this isn't determined by where a regular function is defined — it's determined by how the function is called.

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

obj.greet();          // "Alice" — called as obj.greet(), so this = obj
const fn = obj.greet;
fn();                 // undefined (or TypeError in strict mode) — called plainly, this is not obj anymore

fn() calls the exact same function as obj.greet(), but this binding depends on the call-site syntax (obj.greet() vs. plain fn()), not on where the function was written. This is exactly why passing a method as a callback (onClick={obj.greet}) silently breaks its this binding — the function is being called plainly by whatever invokes the callback, detached from obj.

The four ways this gets determined

Call patternthis inside the function
obj.method()obj — the object the method was called on
fn() (plain call)undefined in strict mode / classes; the global object in loose, non-strict mode
fn.call(thisArg, ...args) / fn.apply(thisArg, argsArray)Explicitly thisArg, for that one call
new Fn()The newly created object
Arrow functionNot its own — inherited lexically from the enclosing scope at definition time

Arrow functions are the exception

Arrow functions don't have their own this binding at all — they capture this lexically from the surrounding scope at the time they're defined, exactly like a closure captures a normal variable. This is exactly why arrow functions are so commonly used for callbacks inside methods:

const timer = {
  seconds: 0,
  start() {
    setInterval(() => {
      this.seconds++; // arrow function — `this` is still `timer`, inherited from start()
    }, 1000);
  },
};

const brokenTimer = {
  seconds: 0,
  start() {
    setInterval(function () {
      this.seconds++; // regular function — `this` here is NOT brokenTimer (undefined or the global object)
    }, 1000);
  },
};

Because an arrow function has no this of its own, call/apply/bind cannot change it — they're simply ignored for an arrow function's this.

call, apply, and bind

All three exist to explicitly control what this a function runs with, rather than relying on call-site syntax.

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

const person = { name: "Sam" };

introduce.call(person, "Hi");    // "Hi, I'm Sam" — calls immediately, args listed individually
introduce.apply(person, ["Hi"]); // "Hi, I'm Sam" — calls immediately, args as an array

const boundIntroduce = introduce.bind(person);
boundIntroduce("Hello"); // "Hello, I'm Sam" — returns a NEW function, permanently bound, called later

bind is the one used most often in practice, because it produces a reusable function rather than invoking anything immediately — the standard fix for passing a class method as an event handler without losing its this:

class Toggle {
  constructor() {
    this.on = false;
    this.handleClick = this.handleClick.bind(this); // bind once, in the constructor
  }
  handleClick() {
    this.on = !this.on;
  }
}