The this Keyword

Course: JavaScript Advanced

Lesson 4: The this Keyword

Level

Advanced

Estimated Reading Time

10–15 minutes

Goal

By the end of this lesson, the learner will be able to:

  • Explain how this is determined in different calling contexts
  • Use call, apply, and bind to explicitly set this
  • Explain why arrow functions do not have their own this and when to use them

Frontend Development Context

The this keyword is the source of more JavaScript confusion than almost any other feature. In event listeners, class methods, callbacks, and object methods, this can silently point to the wrong object, causing undefined property errors that are frustrating to debug. Every frontend developer has encountered code where this unexpectedly became the global object or undefined inside a callback.

Understanding how this is determined — and how to control it with call, apply, bind, and arrow functions — is essential for writing reliable class-based components, event handlers, and reusable utility functions. It also explains why React class components require .bind(this) in constructors, why Vue and React hooks favor arrow functions, and how this works inside modern framework internals.

Explanation

In JavaScript, this is not fixed at the time a function is written — it is determined dynamically at the time the function is called, based on how it is called. There are four main rules that determine this.

Default binding: If a regular function is called standalone (not as a method, not with new, not with explicit binding), this is the global object (window in browsers) in non-strict mode, or undefined in strict mode. This is the implicit fallback and often the source of unintended behavior.

Implicit binding: If a function is called as a method of an object — obj.method() — then this inside method refers to obj. However, this binding is fragile: if you extract the method and call it separately (const fn = obj.method; fn()), the implicit binding is lost and you fall back to default binding.

Explicit binding: Function.prototype.call, apply, and bind let you set this explicitly. call(thisArg, arg1, arg2) invokes the function immediately with the given this. apply(thisArg, [args]) does the same but accepts arguments as an array. bind(thisArg) returns a new function permanently bound to thisArg, regardless of how it is later called.

Arrow functions: Arrow functions do not have their own this at all. They capture this from the surrounding lexical scope at the time they are defined, not when they are called. This makes them ideal for callbacks inside methods, where you want this to refer to the outer object rather than the callback's own calling context.

new binding: When a function is called with new, a new object is created, this is set to that new object, and the function's prototype is linked. This is how constructor functions and classes work under the hood.

Code Example

This example demonstrates implicit binding, lost binding, explicit binding with bind, and arrow functions capturing lexical this.

const timer = {
  label: "Dashboard Timer",
  seconds: 0,

  // Regular method — this works fine when called as timer.start()
  start() {
    console.log(`Starting: ${this.label}`);

    // Arrow function captures `this` from start()'s scope = timer
    const tick = () => {
      this.seconds++;
      console.log(`${this.label}: ${this.seconds}s`);
    };

    // Simulate 3 ticks
    tick(); tick(); tick();
  },

  // Demonstrates lost binding
  getLabel() {
    return this.label;
  },
};

timer.start(); // "Starting: Dashboard Timer", then 3 ticks

// Implicit binding — works
console.log(timer.getLabel()); // "Dashboard Timer"

// Lost binding — this becomes undefined (strict) or window
const detached = timer.getLabel;
try {
  console.log(detached()); // TypeError in strict mode
} catch (e) {
  console.log("Lost binding:", e.message);
}

// Explicit binding with bind — permanent fix
const boundGetLabel = timer.getLabel.bind(timer);
console.log(boundGetLabel()); // "Dashboard Timer"

// call — invoke immediately with a different this
const altContext = { label: "Sidebar Timer" };
console.log(timer.getLabel.call(altContext)); // "Sidebar Timer"

// apply — same as call but args as array
function formatTimer(prefix, suffix) {
  return `${prefix} ${this.label} ${suffix}`;
}
console.log(formatTimer.apply(altContext, [">>", "<<"])); // ">> Sidebar Timer <<"

Code Explanation

Inside timer.start(), this is timer because of implicit binding — start is called as a method of timer. The inner tick function is an arrow function, so it doesn't get its own this; it inherits this from start's scope, which is timer. Each tick() call correctly increments timer.seconds and reads timer.label.

The getLabel method works correctly when called as timer.getLabel() — implicit binding makes this refer to timer. But when extracted to detached and called as a standalone function, the implicit binding is lost. In strict mode this throws a TypeError because this is undefined and undefined.label fails.

bind(timer) creates a new function where this is permanently locked to timer. No matter how boundGetLabel is later called — as a callback, assigned to another variable, passed to an event listener — this will always be timer.

call and apply demonstrate immediate invocation with a custom this. call is used with individual arguments; apply is used when the arguments are already in an array (useful for variadic functions or forwarding arguments).

Pattern Highlights

Positive Pattern: Use arrow functions for callbacks inside object methods to capture the outer this lexically — this avoids the need for const self = this workarounds and keeps code clean.

⚠️ Neutral Note: bind creates a new function each time it is called. In React class components, calling .bind(this) inside render() creates a new function on every render — prefer binding in the constructor or using arrow function class properties instead.

Negative Pattern: Do not use arrow functions as object methods when you need this to refer to the object — arrow functions capture this from where they are defined (likely the global scope or enclosing function), not from the object they are assigned to.

Common Mistakes

Mistake 1: Using an arrow function as an object method

Arrow functions don't have their own this, so using one as a method means this will not be the object.

const button = {
  label: "Submit",
  handleClick: () => {
    console.log(this.label); // undefined — this is window or undefined
  },
};

button.handleClick(); // not what you expect

Use a regular function expression or method shorthand (handleClick() {}) so this is bound to button when called as a method.

Mistake 2: Passing a method as a callback without binding

Passing obj.method as a callback loses the implicit binding.

class Carousel {
  constructor() {
    this.index = 0;
    document.querySelector(".next")
      .addEventListener("click", this.advance); // this is lost!
  }

  advance() {
    this.index++; // TypeError: Cannot set properties of undefined
  }
}

Fix: use this.advance.bind(this) or an arrow function wrapper () => this.advance() in the addEventListener call.

Mistake 3: Relying on this inside a setTimeout callback

Regular functions in setTimeout use default binding, so this is not the surrounding object.

const notification = {
  message: "Done!",
  show() {
    setTimeout(function () {
      console.log(this.message); // undefined — this is window
    }, 1000);
  },
};

Replace the regular function with an arrow function: setTimeout(() => { console.log(this.message); }, 1000). The arrow function captures this from show, which is correctly bound to notification.

Quick Recap

  • this in JavaScript is determined by how a function is called, not where it is defined — there are four binding rules: default, implicit, explicit (call/apply/bind), and new.
  • Implicit binding is lost when a method is extracted from its object and called as a standalone function.
  • Arrow functions do not have their own this — they lexically capture this from their surrounding scope at definition time, making them ideal for nested callbacks inside methods.
  • Use bind to permanently lock this for callbacks like event listeners; use call or apply to temporarily invoke a function with a specific this.

Ready to test your understanding? Take the quiz to reinforce what you learned.

© 2026 Ant Skillsv.26.07.07-23:01