completefrontend
Open lab My progress
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 callExplain why obj.method() works but const fn = obj.method; fn() does not.
  • 02
    Pick a fixGive a method its object back with a wrapper, bind, an arrow function field, or thisArg.
  • 03
    Predict callbacksSay what this will be in a timer, an array callback, or an event listener before you run the code.

A method that forgets its object

A method is a function stored inside an object. Inside a method, this means "the object this method is working on". You met this idea in Methods & this.

The last few lessons gave you three rules:

  • The this keyword: JavaScript picks this at the moment a function is called, not when it is written.
  • call, apply & bind: you can choose this yourself.
  • Arrow functions & lexical this: an arrow function has no this of its own. It uses the this from the code around it.

This lesson uses those rules to find and fix one very common bug.

Here is the bug. A method works when you call user.greet(). Then you hand the method to some other code, and it breaks. This is called losing this.

Nothing was deleted. The function still exists, and so does the object. What is missing is the link between them: the call no longer has user in front of the dot.

The rule in one sentence

A normal function does not carry its this with it. To find this, look at the code that finally calls the function. If no object is in front of the dot, the method has lost its object.

Real-life analogyA speed-dial button without the phone

Picture a speed-dial button that you pull off your phone and give to a friend. The button still exists. But it is not on your phone any more, so pressing it can't call your contact. Each fix in this lesson puts the button back on the right phone before anyone 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 does a job, such as greet()
In real life: Handing over only the button
In JavaScript: Writing const fn = obj.method
In real life: Putting the button back on the phone
In JavaScript: Using a wrapper, bind, an arrow field, or thisArg

This lesson covers four places where the bug shows up, in this order:

  1. Saving a method in a variable.
  2. Handing a method to other code, such as setTimeout.
  3. A fix for classes: arrow function fields.
  4. A fix for any code: wrapper functions.

For each one, you will run broken code, see the real error or wrong output, and pick the smallest fix.

A saved method loses its object

STEP THROUGH

The smallest version of the bug takes two lines. First, save a method in a variable. Then, call the variable. We say the method is detached: it has been taken off its object.

The variable holds only the function. It does not remember that the function came from user. Step through the code below. You can also switch to two fixed versions and compare.

Step through a saved method
Step 0 of 4Ready
Your turn: follow the blue line

Guess first: will the call still have user in front of the dot? Then step through the run to check.

Running in
  1. script
Next: line 2
"use strict";  name: "Ada",  greet() {    return "Hi, " + this.name;  },};const fn = user.greet;console.log(fn());
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Choose which line 8 to run

When you pick a different line, the step-through starts again.

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 line 8 out loud. const fn = user.greet means "put the function that is stored at user.greet into fn". It does not mean "put a future call of user.greet() into fn". That difference is the whole bug.

Line 9 calls fn(). There is no dot and no object in front of fn. So the method gets no object. When line 5 tries to read this.name, it fails with a TypeError: the error JavaScript throws when you use a value in a way its type does not allow, like reading a property of undefined.

Left: user.greet(). The greet card is in the user cart, so this is user. Right: const fn = user.greet, then fn(). The same greet card is alone in a box called fn, so this is undefined.user.greet()name: "Ada"greet()userthis = userconst fn = user.greetfn()greet()fnthis = undefined
The same greet function in both. Called from the cart, it works on user. Saved in fn and called alone, it has no object, so this.name throws a TypeError.
Taking a method out with destructuring also loses the objectPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const user = {
  name: "Ada",
  greet() {
    return "Hi, " + this.name;
  },
};
const { greet } = user;
console.log(greet());

Line 8 uses destructuring: a short way to copy a property out of an object into a variable of the same name. const { greet } = user does the same job as const greet = user.greet.

So it is just another way to save the method in a variable. Line 9 calls greet() with nothing in front of it, and it throws the same TypeError.

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

Line 8 puts round brackets around user.greet. These brackets only group. They do not save the function anywhere. The call still reads as user.greet followed by (), so this is user, and it prints Hi, Ada.

This shows that what matters is the line that calls the method, not the line where the method was written.

Outside strict mode, a lone call gets the global objectPop out in the code editor (opens in a new tab)JavaScript
function showThis() {
  return this === globalThis;
}
const fn = showThis;
console.log(fn());

This small script has no "use strict" line at the top. Strict mode is a safer set of rules that you turn on with that line. Without it, a function called alone gets the global object as its this. The global object holds built-in things such as setTimeout, and globalThis is a name for it. So line 5 prints true.

In strict mode, a function called alone gets undefined as its this instead. Then reading this.name throws a TypeError right away, which makes the bug easy to find.

Outside strict mode, this.name reads a property of the global object. You get a wrong value, often undefined, and no error. So the bug hides for longer.

Modules and classes always use strict mode. Most modern code does too, so you will usually see the TypeError.

Why (user.greet)() still works

Brackets do not take the method off its object. The call still starts from user.greet, so this is user. The bug starts only when you save or hand over the function and call it later without the object.

Callbacks and timers

INTERACTIVE

A callback is a function you hand to other code, so that code can call it later. The other code decides when to call it and what this it gets.

When you hand over user.greet, you hand over only the function. You do not hand over the call user.greet().

setTimeout(user.greet, 0). The user cart holds name Ada and greet. An arrow labeled only greet leads to a lone greet card that the timer gets. Under it: no user.setTimeout(user.greet, 0)name: "Ada"greet()useronly greetgreet()the timer getsno user
user.greet takes the function out of the cart. The timer gets only that function. When the timer calls it later, there is no user in front of the dot.

You will see the bug in four common shapes:

  • A saved method: const fn = user.greet; fn().
  • A timer: setTimeout(user.greet, 0) runs the function later, but not as user.greet().
  • An event listener: when a button is clicked, the browser calls your function with the button as this, not your counter object.
  • An array callback: [1, 2].forEach(logger.log) calls log once for each item, with no object. forEach takes a second argument, called thisArg, that sets this for each call.
Bind the method once, then give it to the timerPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const user = {
  name: "Ada",
  greet() {
    console.log("Timer for " + this.name);
  },
};
const later = user.greet.bind(user);
setTimeout(later, 0);

bind makes a new function whose this is fixed for good. Line 8 makes that new function, later, with this fixed to user.

Line 9 gives later to the timer. When the timer calls it, it prints Timer for Ada. Use this shape when you give the same callback to a timer more than once.

A wrapper can pass the callback's argument onPop out in the code editor (opens in a new tab)JavaScript
"use strict";
const user = {
  name: "Ada",
  greet(label) {
    return label + ": " + this.name;
  },
};
const later = (label) => user.greet(label);
console.log(later("Welcome"));

A wrapper is a small new function whose only job is to call the method the right way. On line 8, the wrapper takes a label and calls user.greet(label), with user in front of the dot.

So line 9 prints Welcome: Ada. If you handed over user.greet alone, it would lose user before it could use the label.

Now try all four shapes in the lab below. Pick a case and press Run to see the broken result. Then turn the fix on and run it again.

Lost this lab
Saved 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());
What it printedbroken
Run a broken case. Then turn its fix on and run it again.
Step 0 of 4Saved method

This code is broken on purpose. Find the line that hands a method to other code as a lone function.

Four common ways to lose this. The event listener case clicks a real button. In the timer case, the wrong value depends on where the code runs, but the timer never calls user.greet().
Real-life analogyThe host gives your function a name badge

You send a speaker to an event, and the host introduces them on stage. The host hands the speaker a name badge. If the speaker expected a counter badge but the host gives them a button badge, the speaker talks about the wrong thing. In the same way, your method reads and changes the wrong object.

In real life: You send a speaker to a conference host
In JavaScript: You hand a function to other code
In real life: The host chooses the speaker's name badge
In JavaScript: The other code chooses the callback's this
In real life: A badge that says Button, not Counter
In JavaScript: An event listener gets the button as this

Bound class fields

STEP THROUGH

Classes come later in the course, in Class basics and Private fields & methods. For now, you need only a few facts:

  • A class is a recipe for making objects of the same shape.
  • new Counter() makes one new object from the Counter recipe. Each object made this way is called an instance.
  • A field is a line like count = 0; inside the class. It gives every new instance its own property.
A counter method that is safe to hand overPop out in the code editor (opens in a new tab)JavaScript
class Counter {  count = 0;  increment = () => {    this.count++;  };}

Line 3 is a field whose value is an arrow function. An arrow function has no this of its own. It uses the this from the code around it. Here, that is the new instance being made.

So each instance gets an increment that is already tied to that instance. You can hand it to a timer or a button, and it still changes the right counter. We call this a bound class field.

The step-through below compares it with a normal class method. A normal method is stored once, on the class's prototype: a shared object that every instance looks in for methods.

Arrow fields and normal methods, side by side
Step 0 of 6Ready
Your turn: follow the blue line

Classes come later in the course. For now, notice the trade-off: arrow fields are safe to hand over, while normal methods are shared.

Running in
  1. script
Next: line 1
  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);
CallStoreChangeResultBlue line: runs next. 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.
Top: one.increment === two.increment is false. The one cart and the two cart each hold count 0 and their own increment card. Bottom: a.increment === b.increment is true. The a cart and the b cart each hold only count 0. Both point to one shared increment card between them.one.increment === two.incrementcount: 0incrementonefalsetwo copiescount: 0incrementtwoa.increment === b.incrementcount: 0aincrementcount: 0btrueone shared
Top: with an arrow field, every object made from BoundCounter gets its own increment, so the comparison is false. Bottom: objects made from PrototypeCounter share one increment, so the comparison is true.

The trace shows a trade-off:

  • A normal method is shared by all instances. That saves memory. But it still loses its object when you hand it over.
  • A bound class field makes one new function for each instance. That uses a little more memory. But it is safe to hand to timers and event listeners.

Use a bound class field when a method is meant to be used as a callback.

Wrapper functions

COMPARISON

A wrapper is the clearest fix. Instead of handing over the method, you hand over a small new function whose only job is to call the method the right way.

For example, instead of setTimeout(user.greet, 0), write setTimeout(() => user.greet(), 0). The timer calls the wrapper. Then the wrapper calls user.greet(), with user in front of the dot.

setTimeout(() => user.greet(), 0). The timer gets a wrapper card, () => user.greet(). An arrow labeled later leads to the user cart, which holds name Ada and greet. Under the cart: Timer for Ada.setTimeout(() => user.greet(), 0)() => user.greet()the timer getslatername: "Ada"greet()userTimer for Ada
The timer gets the wrapper, not greet. When the timer runs the wrapper, the wrapper calls user.greet() with user in front of the dot. So it prints Timer for Ada.

You have now seen four fixes. The table below compares them. Each row is one fix: what you write, when it helps most, and what to watch out for.

Four ways to give a method its object back
FixWhat you writeWhen it helps mostWhat to watch out for
A wrapper() => obj.method()When you want to call the method on the object at the moment the wrapper runs. It can also pass arguments on.You write a small new function at each place you hand the method over.
bindobj.method.bind(obj)When you want one saved function you can hand over many times. Its this is fixed for good.Each bind call makes a different new function, so two bound copies are never equal.
A bound class fieldincrement = () => { this.count++; }When you write the class and the method is meant for timers or buttons.Each instance gets its own copy of the function, instead of sharing one.
thisArgarray.forEach(obj.method, obj)When the code you call takes a thisArg, like forEach. You need no wrapper.It only works where the code you call accepts a thisArg.

Sometimes one object has several methods that you hand out as callbacks. Then you can bind them all once, at the start. The helper below does that.

Be careful: the helper changes the object you give it. It replaces each named method with a bound copy.

Bind several methods at oncePop 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());

Line by line:

  1. Lines 2 and 3 go through each name in the list. For each one, they replace the method with obj[name].bind(obj), a copy whose this is fixed to obj.
  2. Lines 8 to 14 make a cart object with an add method, and bind add with the helper.
  3. Line 16 saves cart.add in a variable, which would normally lose the object. Line 17 calls it, and it still prints 1, because add was already bound.

Finding the bug: look at the call

SORTER

When a method gets the wrong this, don't guess. Go through these steps in order:

  1. Find the code that finally calls the function. Which object, if any, is in front of the dot?
  2. For a moment, add console.log(this) inside the method. In a browser, click the logged value to open it.
  3. Add console.trace() inside the method. It prints the list of functions that led to this call, so you can see who called it.
  4. Pick the smallest fix: a wrapper, bind, a bound class field, or thisArg.
  5. If you use TypeScript, turn on its noImplicitThis setting. It warns you when the type of this is unclear.

Now sort the calls below. Don't sort by where the function was written. Sort by who makes 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 line in the group it belongs to. Look at the code that makes the call, not at the method's 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 it was written in."

A normal function does not. The same function can get a different this each time, depending on how it is called.

"Passing obj.method is the same as passing obj.method()."

They are different. obj.method hands over the function, to be called later. obj.method() calls it right now and hands over what it returns.

"bind changes the original method."

bind leaves the original alone and returns a new function. Save that new function if you want to use it again.

"Arrow fields are always better than normal methods."

Arrow fields are safe to hand over, but each instance gets its own copy. Normal methods are shared, and they work well when you call them as obj.method().

"A timer should remember the object."

A timer only stores a function to call later. It does not keep the object, unless you use a wrapper or bind.

Shrinking your code cannot cause this bug

A minifier is a tool that makes code smaller before it goes on a website. A correct minifier never changes what the code does. It may rename local variables and remove spaces. But it cannot turn user.greet() into a call without user. When you look for this bug, check the call that actually runs.

Practice: give the method its object back

5 EXERCISES
Exercise 1 · Warm-upPredict the saved-method call

Predict what happens: does the code print a greeting, print undefined, or throw an error?

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 it with bind

    Use bind so a saved speak function still reads pet.name. Then enter what it prints.

    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 it with a wrapper

      Write the saved function as a wrapper that calls the method on its object. Then enter what it prints.

      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 · ChallengeTurn the method into a bound class field

        Make increment safe to hand over by writing it as an arrow function field. Then enter what the saved click prints.

        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 the thisArg of forEach

          Fix the array callback. Pass the object as the second argument of forEach, its thisArg. Then enter the two printed lines.

          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

            Each answer explains why it is right or wrong. For each one, find the code that makes the final call.

            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7Which call makes this equal 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 line makes a function that calls the method on user each time it runs?

              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 use more memory than normal methods?

              Choose an answer to see the explanation.

            6. Question 6 of 7What does this class field print when it is called alone?

              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 7When this is wrong, what should you ask first?

              Choose an answer to see the explanation.

            Key takeaways

            • JavaScript picks this when a normal function is called, not when it is written.
            • A saved method loses its object: const fn = obj.method; fn().
            • Callbacks and timers get only the function. They do not get the object it came from.
            • A wrapper, bind, a bound class field, or thisArg each give the method its object back.
            • Bound class fields are handy for callbacks, but they make one function for each instance.

            In one sentence.
            Losing this means a method was called without its object. To fix it, make sure the object is in front of the dot when the method is finally called.

            Up next: Functions are objects. You will see that functions are values too, so you can store them, give them names, look inside them, and wrap them.

            CompleteFrontend Clear concepts. Working examples.