Skip to main content

Losing the this context

Losing the context means calling an object's method without going through the object, so that this inside it no longer points to that object. In JavaScript this is not baked into a function at declaration time: it is resolved on every call, based on how the function was invoked.

Theory

TL;DR

  • this is set by the call site: what stands to the left of the dot matters, not where the function was declared.
  • Passing a method by reference (setTimeout(user.sayHi)) breaks the link to the object.
  • bind(obj) returns a new function with a permanently fixed context.
  • Arrow functions have no this of their own and take it lexically from the enclosing scope.
  • call / apply set the context temporarily, only for the duration of one call.
  • In classes you fix the context with bind in the constructor or with a class field holding an arrow function.

Quick example

javascript
const user = { name: 'Oleh', sayHi() { console.log(`Hi, I am ${this.name}`); } }; user.sayHi(); // "Hi, I am Oleh" setTimeout(user.sayHi, 1000); // "Hi, I am undefined"

Why this gets lost

setTimeout receives only a reference to the function and invokes it as callback(), not as user.sayHi(). There is no object to the left of the dot any more, so the binding to user disappears:

  • in strict mode (and ES modules are always strict) this is undefined, and reading this.name throws a TypeError;
  • in sloppy mode this is substituted with the global object (window in the browser), and this.name quietly returns undefined.

The same happens with any detached method: event handlers, array.map(obj.method), storing a method in a variable.

Pinning the context: bind, call and apply

Function.prototype.bind() creates a new function with a hard-wired context, no matter how it is called later:

javascript
setTimeout(user.sayHi.bind(user), 1000); // "Hi, I am Oleh"

call and apply, by contrast, do not create a new function: they invoke the existing one with the given context right now. That is a temporary binding, valid only for this call:

javascript
function greet() { console.log(`Hi, ${this.name}`); } const user = { name: 'Oleh' }; greet.call(user); // "Hi, Oleh" greet.apply(user); // "Hi, Oleh"

The only difference between them is argument passing: call takes them as a list, apply as an array.

Arrow functions, the self variable and wrappers

An arrow function has no this of its own: it takes it from the scope where it was created. Inside a method it therefore closes over the correct context:

javascript
const user = { name: 'Oleh', sayHiLater() { setTimeout(() => { console.log(`Hi, I am ${this.name}`); }, 1000); } }; user.sayHiLater(); // "Hi, I am Oleh"

Before arrow functions existed, the same effect was achieved by saving the context into a self or that variable:

javascript
const user = { name: 'Oleh', sayHiLater() { const self = this; setTimeout(function() { console.log(`Hi, I am ${self.name}`); }, 1000); } }; user.sayHiLater(); // "Hi, I am Oleh"

The simplest option, when you control the place where the callback is passed, is not to pass the method directly at all but to call it inside an anonymous wrapper:

javascript
setTimeout(() => user.sayHi(), 1000); // "Hi, I am Oleh"

Here user.sayHi() is called through the object, so the context is correct.

Context inside classes

In a class this usually survives, as long as the method is called through an instance:

javascript
class User { constructor(name) { this.name = name; } sayHi() { console.log(`Hi, ${this.name}`); } } const oleh = new User('Oleh'); oleh.sayHi(); // "Hi, Oleh"

But detach the method from the instance and the context is gone again (class bodies always run in strict mode, so this throws):

javascript
const fn = oleh.sayHi; fn(); // TypeError: Cannot read properties of undefined

The classic fix is binding in the constructor:

javascript
class User { constructor(name) { this.name = name; this.sayHi = this.sayHi.bind(this); // fix the context once and for all } sayHi() { console.log(`Hi, ${this.name}`); } } const oleh = new User('Oleh'); setTimeout(oleh.sayHi, 1000); // "Hi, Oleh"

This is exactly how React class components were written for years: this.handleClick = this.handleClick.bind(this).

The more modern option is class fields (ES2022): a method declared as an arrow function automatically inherits the instance's this:

javascript
class User { name = 'Oleh'; sayHi = () => { console.log(`Hi, ${this.name}`); }; } const oleh = new User(); setTimeout(oleh.sayHi, 1000); // "Hi, Oleh"

Class field syntax is supported by every modern browser. The price is that such a function is created per instance instead of living once on the prototype.

Summary table

ApproachWhat it doesWhere it is used
.bind(this)Creates a new function with a fixed contextCallbacks, React, timers
Arrow functionInherits this from the enclosing scopesetTimeout, async, promises
const self = thisThe old way to keep a reference to the contextLegacy code, callbacks
call / applySets the context temporarily, for one callOne-off invocations
Binding in a classbind in the constructor or an arrow class fieldReact classes, OOP
Anonymous wrapperCalls the method through the objectSimple callbacks

Common mistakes

  • Assuming this depends on where the function is declared. It depends only on how it is called; the same function can have a different this on every call.
  • Calling bind inside render or inside a handler. bind returns a new function every time, so reference comparison breaks and removeEventListener cannot detach the listener. Bind once, in the constructor or in a class field.
  • Trying to "reassign" this on an arrow function. arrow.call(obj) and arrow.bind(obj) have no effect: an arrow has no this of its own.
  • Using an arrow as an object literal method. { name: 'Oleh', sayHi: () => this.name } takes this from the enclosing scope, not from the object.
  • Forgetting that the failure is silent. In sloppy mode nothing throws: you simply see undefined instead of the value.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.