cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Losing this

Troubleshoot methods that forget their object when they are detached, passed to callbacks, scheduled with setTimeout, or used as event handlers.

By the end, you can
  • 01
    Spot the broken call siteExplain why obj.method() works but const fn = obj.method; fn() does not.
  • 02
    Choose a repairUse a wrapper, bind, a class-field arrow, or a thisArg parameter intentionally.
  • 03
    Debug callbacks confidentlyPredict what this is in timers, array callbacks, and event listeners before you run them.

The disappearing receiver

In Methods & this, you saw that a method can use this to reach the object that called it. Lesson 15.1, The this keyword, names the rule: JavaScript decides this from the call site. Lesson 15.2, call, apply & bind, shows how to set it explicitly. Lesson 15.3, Arrow functions & lexical this, explains why arrows do not make their own this. This lesson turns those rules into troubleshooting muscle memory.

The practical problem is simple: a method works when you call user.greet(), then breaks when you pass it somewhere else. That is losing this. The function was not deleted. The object was not deleted. The relationship between the function call and the object disappeared.

The one-sentence rule

A regular function does not carry its this around. Look at the expression that finally calls it. If there is no receiver before the dot, the method has probably lost the object it expected.

Real-life analogyA speed-dial button without the phone

Losing this is like giving someone your phone’s speed-dial button without the phone. The button still exists, but the contact it pointed to is gone. Every fix in this lesson reattaches the button to the right phone before someone presses it.

In real life: A phone stores contacts
In JavaScript: An object stores data such as name or count
In real life: A speed-dial button starts a call
In JavaScript: A method starts behavior such as greet()
In real life: Handing over only the button
In JavaScript: Assigning const fn = obj.method
In real life: Reattaching the button to the phone
In JavaScript: Using a wrapper, bind, a class-field arrow, or thisArg

Where the analogy stops: A real button may physically know which phone it belongs to. A JavaScript function value usually does not; regular this is selected by the next call.

We will cover detached methods, callbacks and setTimeout, bound class fields, and wrapper functions, in that order. Along the way you will run broken snippets, see the real error or wrong output, and choose the smallest repair.

Detached methods

STEP THROUGH

The smallest lost-this bug has two lines: copy a method, then call the copy. The copy is only a function value. It does not remember that it came from user.

Trace the detached method
Step 0 of 4Ready
Your turn: follow the blue line

Predict the call-site rule first. Then step through the recorded run to see whether the object before the dot survives.

Running in
  1. script
Next: line 2
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
"use strict";  name: "Ada",  greet() {    return "Hi, " + this.name;  },};const fn = user.greet;console.log(fn());
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
Choose the call shape to replay

Changing the call shape starts a fresh recording.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Read the broken version aloud: const fn = user.greet means “put the function currently stored at user.greet into fn.” It does not mean “put a future call to user.greet() into fn.” That difference is the whole bug.

In strict code, a plain call like fn() uses undefined as its default this. Then the method tries to read this.name, and a TypeError is the honest result. In older non-strict scripts, JavaScript may substitute a global object instead, which often turns the bug into a confusing undefined value rather than an immediate crash.

Why (user.greet)() still works

Parentheses do not detach the method reference. The call still comes from the user.greet reference, so this is user. The bug starts when you store or pass the function value and call it later without the receiver.

Callbacks & setTimeout

INTERACTIVE

A callback is a function you hand to another piece of code. That other code decides when and how to call it. If you hand over user.greet, you handed over a function, not the method call user.greet().

Four common callback shapes are worth recognizing on sight:

  • Detached method: const fn = user.greet; fn().
  • Timer: setTimeout(user.greet, 0) runs later without calling through user.
  • Event listener: browser event listeners usually receive the element as this, not your counter object.
  • Array callback: [1, 2].forEach(logger.log) calls log as a callback. Use its second thisArg parameter when you want this to be logger.
This detective lab
Detached method · brokenPop out in the code editor (opens in a new tab)JavaScript
"use strict";const user = {  name: "Ada",  greet() {    return "Hi, " + this.name;  },};const fn = user.greet;console.log(fn());
Real runbroken
Run a broken case, then toggle its fix.
Step 0 of 4Detached method

This is intentionally broken. Look for the line that hands a method away as a plain function value.

Four common lost-this bugs. The button case uses a real button element and a real click; timer results vary by host, but the method is still not called as user.greet().
Real-life analogyThe callback gets a new name badge

Passing a callback is like letting an event host introduce your function on stage. The host may give it a different name badge. If your method expected the counter badge but the host gives it a button badge, the method reads and writes the wrong object.

In real life: You lend a badge to a conference host
In JavaScript: You pass a function to an API
In real life: The host decides what badge you wear on stage
In JavaScript: The API decides the callback's this
In real life: A badge that says Button, not Counter
In JavaScript: An event listener called with this as the element

Where the analogy stops: Some APIs do not use this at all, and arrows ignore the badge because they keep lexical this. Always check the API you are calling.

Bound class fields

STEP THROUGH

Classes are a Stage 5 topic in this course, especially Class basics and Private fields & methods. For this lesson you only need one minimal pattern:

A callback-safe counter methodPop out in the code editor (opens in a new tab)JavaScript
class Counter {  count = 0;  increment = () => {    this.count++;  };}

increment = () => ... is a public field whose value is an arrow function. The arrow does not create a fresh this; it uses the instance being initialized. That makes the method already glued to the counter when you pass it to a callback.

Compare bound fields and prototype methods
Step 0 of 6Ready
Your turn: follow the blue line

Class syntax comes later in the course. For now, focus on the trade-off: class-field arrows are callback-safe, while prototype methods are shared.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
  count = 0;  increment = () => {    this.count++;    return this.count;  };} class PrototypeCounter {  count = 0;  increment() {    this.count++;    return this.count;  }} const one = new BoundCounter();const two = new BoundCounter();const a = new PrototypeCounter();const b = new PrototypeCounter();console.log(one.increment === two.increment);console.log(a.increment === b.increment);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

The trace proves the trade-off. A prototype method is shared by all instances, so it is memory-efficient. A bound class field creates one function per instance, so it costs more function objects, but it is safe to hand to event listeners and timers. Use it when a method is designed to be used as a callback.

Wrapper functions

COMPARISON

A wrapper is the most explicit fix: hand over a new function whose only job is to make the method call correctly. Instead of setTimeout(user.greet, 0), write setTimeout(() => user.greet(), 0). The timer calls the wrapper, and the wrapper calls user.greet() with the object before the dot.

Four ways to reattach this
FixShapeBest forWatch out
Wrapper arrow() => obj.method()Keeps arguments flexible; best when you want to call the current object at run time.Creates a tiny extra function and must be written at each callback site.
bindobj.method.bind(obj)Creates a reusable function with this permanently attached.Harder to undo; repeated binding creates new function identities.
Bound class fieldincrement = () => { this.count++; }Great for UI callbacks because each instance method is already glued.One function per instance instead of one shared prototype method.
thisArg parameterarray.forEach(obj.method, obj)Built into many array methods; no wrapper needed.Only works for APIs that actually accept a thisArg.

When an object has several callback methods, you may also bind them once. The helper below mutates the object, so use it deliberately. Notice the object uses inline method syntax; that keeps this lesson’s demos safe from production minifier rewrites that can accidentally change a local method call shape.

Binding several methods deliberatelyPop out in the code editor (opens in a new tab)JavaScript
function bindMethods(obj, names) {  for (const name of names) {    obj[name] = obj[name].bind(obj);  }  return obj;} const cart = bindMethods({  items: 0,  add() {    this.items++;    return this.items;  },}, ["add"]); const addLater = cart.add;console.log(addLater());

Practical debugging: look at the call site

SORTER

When a method sees the wrong this, resist guessing. Walk through this checklist:

  1. Find the final call expression. What object, if any, is before the dot?
  2. Temporarily log this inside the method. In a browser, expand the value.
  3. Use console.trace() inside the method to see who called it.
  4. Choose the smallest fix: wrapper, bind, class-field arrow, or thisArg.
  5. In TypeScript projects, consider noImplicitThis so unclear this types become warnings.

Sort the call shapes below. Do not sort by where the function was defined. Sort by who performs the final call.

Will this be lost?
  • user.greet()
  • const fn = user.greet; fn()
  • () => user.greet()
  • user.greet.bind(user)
  • setTimeout(user.greet, 0)
  • items.forEach(logger.log, logger)
  • button.addEventListener("click", counter.increment)
  • (user.greet)()
Try it yourself
0 of 8 correct

Put each expression where it belongs. Focus on the actual call site, not the method body.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common misconceptions

“A method remembers the object where it was written.”

Regular functions do not. The same function body can run with different this values depending on the call site.

“Passing obj.method is the same as passing obj.method().”

The first passes a function value for later. The second calls immediately and passes the return value.

“bind changes the original method.”

bind returns a new function. Store that function if you want to reuse it.

“Arrow fields are always better than prototype methods.”

Arrow fields are callback-safe, but each instance gets its own function. Prototype methods are shared and excellent when you call them as methods.

“A timer is special, so it should remember the object.”

Timers schedule callbacks. They do not preserve the method receiver unless you wrap or bind the method yourself.

Production demo safety

This lesson avoids the minifier hazard for this demos by using inline methods in object literals or module-level functions. That way production builds do not rewrite a local object method call into a detached function call and teach the wrong rule.

Practice: repair lost this

5 EXERCISES
Exercise 1 · Warm-upPredict the detached call

Predict whether the snippet prints a greeting, prints undefined, or throws.

Starter codePop out in the code editor (opens in a new tab)JavaScript
"use strict";
const user = {
  name: "Ada",
  greet() {
    return "Hi, " + this.name;
  },
};
const fn = user.greet;
console.log(fn());

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeFix with bind

    Use bind so a detached speak callback still reads pet.name.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    "use strict";
    const pet = {
      name: "Pixel",
      speak() {
        return this.name + " chirps";
      },
    };
    const speak = pet.speak.bind(pet);
    console.log(speak());

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix with a wrapper

      Write the callback as a wrapper that calls the method on the object.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      "use strict";
      const queue = {
        next: "Ada",
        announce() {
          return "Next: " + this.next;
        },
      };
      const later = () => queue.announce();
      console.log(later());

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · ChallengeConvert to a bound class field

        Make increment safe to pass around by writing it as a class-field arrow.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        class Counter {
          count = 0;
          increment = () => {
            this.count++;
            return this.count;
          };
        }
        const counter = new Counter();
        const click = counter.increment;
        console.log(click());

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeUse forEach thisArg

          Repair the array callback by passing the object as forEach’s thisArg.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          "use strict";
          const logger = {
            prefix: "item ",
            log(value) {
              console.log(this.prefix + value);
            },
          };
          [1, 2].forEach(logger.log, logger);

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS

            Every wrong answer explains the call-site mistake. Take your time and trace the final call.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which call site sets this to user?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does this print or throw?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              const user = {
                name: "Ada",
                greet() {
                  console.log(this.name);
                },
              };
              const greet = user.greet;
              greet();

              Choose an answer to see the explanation.

            3. Question 3 of 7Which fix keeps the method attached but still calls the current user object each time?

              Choose an answer to see the explanation.

            4. Question 4 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              "use strict";
              const logger = {
                prefix: "#",
                log(value) {
                  console.log(this.prefix + value);
                },
              };
              [1, 2].forEach(logger.log, logger);

              Choose an answer to see the explanation.

            5. Question 5 of 7Why can bound class fields cost more memory than prototype methods?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does this class-field callback print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              class Counter {
                count = 0;
                increment = () => {
                  this.count++;
                  return this.count;
                };
              }
              const counter = new Counter();
              const inc = counter.increment;
              console.log(inc());

              Choose an answer to see the explanation.

            7. Question 7 of 7Which debugging question should you ask first when this is wrong?

              Choose an answer to see the explanation.

            Key takeaways

            • this is chosen when a regular function is called, not when it is defined.
            • Detached methods lose their receiver: const fn = obj.method; fn().
            • Callbacks and timers receive function values; they do not preserve method receivers.
            • Wrappers, bind, bound class fields, and thisArg parameters all reattach this in different ways.
            • Bound class fields are convenient for callbacks, but they create one function per instance.

            Remember the one-liner.
            Losing this means a method was called without the object it expected; fix it by making the call site explicit again.

            Up next: Functions are objects, where you will see why functions can be stored, named, inspected, wrapped, and decorated.

            CompleteFrontend Clear concepts. Working examples.