Losing this
Troubleshoot methods that forget their object when they are detached, passed to callbacks, scheduled with setTimeout, or used as event handlers.
- 01Spot the broken callExplain why
obj.method()works butconst fn = obj.method; fn()does not. - 02Pick a fixGive a method its object back with a wrapper,
bind, an arrow function field, orthisArg. - 03Predict callbacksSay what
thiswill 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
thisat the moment a function is called, not when it is written. - call, apply & bind: you can choose
thisyourself. - Arrow functions & lexical this: an arrow function has no
thisof its own. It uses thethisfrom 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.
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.
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
nameorcount - 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, orthisArg
This lesson covers four places where the bug shows up, in this order:
- Saving a method in a variable.
- Handing a method to other code, such as
setTimeout. - A fix for classes: arrow function fields.
- 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 THROUGHThe 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.
Guess first: will the call still have user in front of the dot? Then step through the run to check.
script
"use strict"; name: "Ada", greet() { return "Hi, " + this.name; },};const fn = user.greet;console.log(fn());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.
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."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.
"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.
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.
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
INTERACTIVEA 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().
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 asuser.greet(). - An event listener: when a button is clicked, the browser calls your function with the button as
this, not yourcounterobject. - An array callback:
[1, 2].forEach(logger.log)callslogonce for each item, with no object.forEachtakes a second argument, calledthisArg, that setsthisfor each call.
"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.
"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.
"use strict";const user = { name: "Ada", greet() { return "Hi, " + this.name; },};const fn = user.greet;console.log(fn());Run a broken case. Then turn its fix on and run it again.
This code is broken on purpose. Find the line that hands a method to other code as a lone function.
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 THROUGHClasses 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 theCounterrecipe. 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.
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.
Classes come later in the course. For now, notice the trade-off: arrow fields are safe to hand over, while normal methods are shared.
script
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);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
COMPARISONA 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.
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.
| Fix | What you write | When it helps most | What 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. |
bind | obj.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 field | increment = () => { 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. |
thisArg | array.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.
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:
- 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 whosethisis fixed toobj. - Lines 8 to 14 make a
cartobject with anaddmethod, and bindaddwith the helper. - Line 16 saves
cart.addin a variable, which would normally lose the object. Line 17 calls it, and it still prints1, becauseaddwas already bound.
Finding the bug: look at the call
SORTERWhen a method gets the wrong this, don't guess. Go through these steps in order:
- Find the code that finally calls the function. Which object, if any, is in front of the dot?
- For a moment, add
console.log(this)inside the method. In a browser, click the logged value to open it. - Add
console.trace()inside the method. It prints the list of functions that led to this call, so you can see who called it. - Pick the smallest fix: a wrapper,
bind, a bound class field, orthisArg. - If you use TypeScript, turn on its
noImplicitThissetting. It warns you when the type ofthisis unclear.
Now sort the calls below. Don't sort by where the function was written. Sort by who makes the final call.
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)()
Put each line in the group it belongs to. Look at the code that makes the call, not at the method's body.
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.
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 EXERCISESPredict what happens: does the code print a greeting, print undefined, or throw an error?
"use strict";
const user = {
name: "Ada",
greet() {
return "Hi, " + this.name;
},
};
const fn = user.greet;
console.log(fn());It throws a TypeError. Line 8 saves only the function. Line 9 calls fn() with no user in front of the dot, so this is undefined, and reading this.name fails.
Use bind so a saved speak function still reads pet.name. Then enter what it prints.
"use strict";
const pet = {
name: "Pixel",
speak() {
return this.name + " chirps";
},
};
const speak = pet.speak.bind(pet);
console.log(speak());"use strict";
const pet = {
name: "Pixel",
speak() {
return this.name + " chirps";
},
};
const speak = pet.speak.bind(pet);
console.log(speak());bind(pet) returns a new function whose this is fixed to pet. So calling it alone still prints Pixel chirps.
Write the saved function as a wrapper that calls the method on its object. Then enter what it prints.
"use strict";
const queue = {
next: "Ada",
announce() {
return "Next: " + this.next;
},
};
const later = () => queue.announce();
console.log(later());"use strict";
const queue = {
next: "Ada",
announce() {
return "Next: " + this.next;
},
};
const later = () => queue.announce();
console.log(later());Line 9 calls the wrapper. The wrapper then calls queue.announce(), so this is queue. It prints Next: Ada.
Make increment safe to hand over by writing it as an arrow function field. Then enter what the saved click prints.
class Counter {
count = 0;
increment = () => {
this.count++;
return this.count;
};
}
const counter = new Counter();
const click = counter.increment;
console.log(click());class Counter {
count = 0;
increment = () => {
this.count++;
return this.count;
};
}
const counter = new Counter();
const click = counter.increment;
console.log(click());The arrow field uses the this of the new instance. So const click = counter.increment; click() still adds 1 to the counter, and it prints 1.
Fix the array callback. Pass the object as the second argument of forEach, its thisArg. Then enter the two printed lines.
"use strict";
const logger = {
prefix: "item ",
log(value) {
console.log(this.prefix + value);
},
};
[1, 2].forEach(logger.log, logger);"use strict";
const logger = {
prefix: "item ",
log(value) {
console.log(this.prefix + value);
},
};
[1, 2].forEach(logger.log, logger);logger is the second argument, so forEach calls log with this set to logger each time. So this.prefix + value gives item 1, then item 2.
Quiz: check your understanding
7 QUESTIONSEach answer explains why it is right or wrong. For each one, find the code that makes the final call.
Question 1 of 7Which call makes
thisequal touser?Choose an answer to see the explanation.
Question 2 of 7What does this print or throw?
Read the code, then predict"use strict"; const user = { name: "Ada", greet() { console.log(this.name); }, }; const greet = user.greet; greet();Choose an answer to see the explanation.
Question 3 of 7Which line makes a function that calls the method on
usereach time it runs?Choose an answer to see the explanation.
Question 4 of 7What does this print?
Read the code, then predict"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.
Question 5 of 7Why can bound class fields use more memory than normal methods?
Choose an answer to see the explanation.
Question 6 of 7What does this class field print when it is called alone?
Read the code, then predictclass 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.
Question 7 of 7When
thisis wrong, what should you ask first?Choose an answer to see the explanation.
Key takeaways
- JavaScript picks
thiswhen 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, orthisArgeach 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.