Skip to Content
Course content

32: The this Keyword in Depth

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time debugging JavaScript, you've likely hit a wall where this is undefined or, even worse, it's pointing to the window object when you're absolutely certain it should be pointing to your class instance. It's one of the most frustrating parts of the language because it feels unpredictable.

The Myth: "this" Always Refers to the Object it's Defined In

The most common misconception I see is the belief that this is lexically scoped—meaning, that it's "locked" to the object where you wrote the function. It feels intuitive. If you write a method inside a User object, you assume this will always be that user. But JavaScript doesn't work that way. Let's look at why this fails.

const player = {
  name: 'Sora',
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

player.greet(); // "Hello, my name is Sora" - This works!

const shout = player.greet; 
shout(); // "Hello, my name is undefined" (or an error in strict mode)

Wait, what happened? We took the exact same function and just gave it a different name. But when we called shout(), this vanished. This is because this isn't about where the function was defined; it's about how the function was called.

The Reality: It's All About the Call Site

In JavaScript, the value of this is determined at runtime. I like to tell my juniors to look at the "call site"—the actual line of code where the function is executed. If there is a dot to the left of the function call, this is the object before that dot. In player.greet(), the object is player. In shout(), there is no dot, so this defaults to the global object (or undefined if you're using strict mode).

There are four main rules that determine the value of this, in order of priority:

  • New Binding: If you use the new keyword, this is the brand new object being created.
  • Explicit Binding: If you use .call(), .apply(), or .bind(), you are manually telling JavaScript exactly what this should be.
  • Implicit Binding: If you call a function as a method (e.g., obj.method()), this is the object before the dot.
  • Default Binding: If none of the above apply, this is the global window (or undefined).

Forcing the Issue with call, apply, and bind

Sometimes you need to "borrow" a method from one object and use it on another. This is where explicit binding comes in. I use this occasionally when I want to use a utility method from a prototype without inheriting the whole class.

const admin = {
  name: 'Boss',
  permissions: ['read', 'write', 'delete'],
  checkAccess(role) {
    console.log(`${this.name} is checking access for ${role}...`);
  }
};

const guest = { name: 'Visitor' };

// We borrow checkAccess from admin but force 'this' to be guest
admin.checkAccess.call(guest, 'guest-role'); 
// Output: "Visitor is checking access for guest-role..."

.call() and .apply() execute the function immediately. .bind() is different; it doesn't run the function right away. Instead, it returns a new function that is permanently bound to the object you specified. This is a lifesaver when passing methods as callbacks to event listeners.

Arrow Functions: The Lexical Exception

Now, here is the "cheat code." Arrow functions do not have their own this. They don't follow any of the rules I just listed. Instead, they capture the this value of the surrounding code (the lexical scope) at the moment they are created.

If you're inside a class method and you need to run a setTimeout, a regular function will lose its this context because setTimeout calls the function without a "dot" (default binding). An arrow function, however, will remember the this from the method it was written inside. Honestly, this is why arrow functions are used for 90% of callbacks in modern JS.




📋 Practical Task

Fixing the Shopping Cart Timeout Bug

You are building a shopping cart where a "Session Expiring" warning appears after 3 seconds. However, the current code is broken because the this context is lost when the timer fires, resulting in an error when trying to access this.cartName.

Your Task: Modify the startExpiryTimer method so that the console.log correctly prints the name of the cart. You can solve this using either .bind() or by converting the callback to an arrow function.

const ShoppingCart = {
  cartName: "Guest Checkout Cart",
  items: [],
  startExpiryTimer() {
    console.log("Timer started...");
    
    // BUG: 'this' will be undefined or the window object here
    setTimeout(function() {
      console.log(`Warning: ${this.cartName} is about to expire!`);
    }, 3000);
  }
};

ShoppingCart.startExpiryTimer();
Rating
0 0

There are no comments for now.

to be the first to leave a comment.