Question

Access the Correct this Inside a JavaScript Callback

javascriptcallbackthis

Question

I have a constructor function that registers an event handler:

function MyConstructor(data, transport) {
  this.data = data;

  transport.on("data", function () {
    alert(this.data);
  });
}

// Mock transport object
var transport = {
  on: function (event, callback) {
    setTimeout(callback, 1000);
  }
};

var obj = new MyConstructor("foo", transport);

Inside the callback, this.data does not refer to the MyConstructor instance that was created. Instead, this appears to refer to a different object.

I also tried passing a prototype method as the callback:

function MyConstructor(data, transport) {
  this.data = data;
  transport.on("data", this.alert);
}

MyConstructor.prototype.alert = function () {
  alert(this.data);
};

However, this has the same problem. How can I access the correct object instance inside the callback?

Short Answer

By the end of this page, you will understand that JavaScript's this is determined by how a function is called, not where it was written. You will learn reliable ways to keep an instance as this in callbacks: bind, arrow functions, and callback APIs that explicitly set a receiver.

Concept

A regular JavaScript function gets its this value at the moment it is called.

When this constructor runs:

transport.on("data", function () {
  alert(this.data);
});

the callback is passed to transport.on. Later, the mock transport calls it like this:

setTimeout(callback, 1000);

That is not a method call on your MyConstructor instance. The callback is effectively called as a standalone function, so it does not automatically receive the instance as this.

The same rule affects this code:

transport.on("data", this.alert);

Although this.alert is retrieved from the instance, the function is later called by transport, not as obj.alert(). Retrieving a method and passing it elsewhere loses its original receiver unless you preserve it.

This matters in event listeners, timers, promise callbacks, API clients, UI components, and asynchronous code. Whenever a method depends on instance state, make sure its callback context is intentional.

Mental Model

Think of this as the person holding a tool when the tool is used, not the person who originally owned the tool.

  • obj.alert() means obj is holding and using alert, so this is obj.
  • setTimeout(obj.alert, 1000) hands the tool to someone else. When the timer uses it, obj is no longer identified as the holder.
  • obj.alert.bind(obj) attaches a label saying: “Whenever this tool is used, treat obj as the holder.”

An arrow function behaves differently: it does not create its own this. It uses the this from the surrounding constructor function.

Take Quiz

Syntax and Examples

A callback that needs instance data must either capture the surrounding this or be permanently bound to the instance.

Option 1: Use an arrow function

Arrow functions use the this from the enclosing scope. Since the constructor is called with new, its this is the newly created object.

function MyConstructor(data, transport) {
  this.data = data;

  transport.on("data", () => {
    console.log(this.data);
  });
}

var transport = {
  on: function (event, callback) {
    setTimeout(callback, 1000);
  }
};

var obj = new MyConstructor("foo", transport);
// Logs: foo

Option 2: Bind a regular function

bind creates a new function whose this is fixed to the value provided.

function MyConstructor(data, transport) {
  this.data = data;

  transport.on("data", function () {
    console.log(this.data);
  }.bind(this));
}

Option 3: Bind a prototype method once

Prototype methods are useful because their implementation is shared by all instances. Bind the method before giving it to another API.

function MyConstructor(data, transport) {
  this.data = data;
  this.handleData = this.handleData.bind(this);

  transport.on("data", this.handleData);
}

MyConstructor.prototype.handleData = function () {
  console.log(this.data);
};

Binding once is especially important when an API requires the same function reference later, such as when removing an event listener.

Step by Step Execution

Consider this version:

function MyConstructor(data, transport) {
  this.data = data;
  transport.on("data", () => {
    console.log(this.data);
  });
}

var obj = new MyConstructor("foo", transport);
  1. new MyConstructor("foo", transport) creates a new object.
  2. Inside the constructor, this refers to that new object.
  3. this.data = data stores "foo" on the instance.
  4. The arrow function is created inside the constructor.
  5. Unlike a regular function, the arrow function does not get a new this when the timer invokes it.
  6. After one second, transport invokes the callback.
  7. The callback uses the constructor's saved this, which is obj.
  8. this.data therefore evaluates to "foo".

With an unbound regular function instead:

transport.on("data", function () {
  console.log(this.data);
});

the timer chooses the callback's this value. It does not know that the callback originally came from obj, so it cannot reliably access obj.data.

Real World Use Cases

  • DOM event handlers: A class instance listens for button clicks and updates its own state.
  • WebSocket or stream events: A client receives incoming data and stores it on a connection manager instance.
  • Timers: A game, polling service, or countdown updates instance properties in setInterval callbacks.
  • Promise chains: A service fetches API data and calls an instance method after a request resolves.
  • Node.js event emitters: A parser or server object registers a method for data, error, or close events.

Example with a timer in a class:

class Counter {
  constructor() {
    this.value = 0;
  }

  start() {
    setInterval(() => {
      this.value += 1;
      console.log(this.value);
    }, 1000);
  }
}

The arrow function keeps this pointing to the Counter instance.

Take Quiz

Real Codebase Usage

In production code, developers usually choose one of these patterns.

Inline arrow callback for short event logic

socket.on("data", (message) => {
  this.messages.push(message);
});

This is concise when the logic is specific to that registration.

Bound named method for reusable logic

class MessageStore {
  constructor(socket) {
    this.messages = [];
    this.onMessage = this.onMessage.bind(this);
    socket.on("message", this.onMessage);
  }

  onMessage(message) {
    this.messages.push(message);
  }
}

A named method is easier to test, reuse, log, and unregister.

Preserve the callback reference for cleanup

class ConnectionMonitor {
  constructor(transport) {
    this.transport = transport;
    this.onClose = this.onClose.bind(this);
    transport.on("close", this.onClose);
  }

  stop() {
    this.transport.off("close", this.onClose);
  }

  onClose() {
    console.log("Connection closed");
  }
}

Calling bind inside both on and off would create two different functions, preventing cleanup. Store the bound function instead.

Some event APIs intentionally set this to the event emitter or target. Check the API documentation before assuming an arrow function is appropriate; arrows ignore a caller-provided this.

Common Mistakes

Passing a method without binding it

transport.on("data", this.handleData); // Context is not preserved.

Avoid it: bind the method in the constructor, or wrap the call in an arrow function.

transport.on("data", this.handleData.bind(this));

If you need to remove the listener later, do not bind inline. Save the bound result.

Expecting a regular callback to inherit this

transport.on("data", function () {
  console.log(this.data); // Not necessarily the instance.
});

Avoid it: use an arrow callback or .bind(this).

Using an arrow function when the API provides a useful this

button.addEventListener("click", () => {
  console.log(this); // The surrounding object, not necessarily the button.
});

In a DOM listener, use a regular function if you specifically need this to be the element:

button.addEventListener("click", function () {
  console.log(this); // The button element.
});

Or use event.currentTarget, which is often clearer.

Binding repeatedly

transport.on("data", this.handleData.bind(this));
transport.off("data", this.handleData.bind(this)); // Different function object.

Avoid it: create the bound function once and store it.

Reading the wrong property

The original prototype example used this.name, but the constructor stored this.data. Use the same property name:

MyConstructor.prototype.alert = function () {
  alert(this.data);
};

Assuming this has one universal fallback value

For a plain function call, this depends on strict mode and runtime environment. In strict mode it is undefined; in other situations it may be a global-like or host-provided object. Do not rely on that behavior for instance access.

Comparisons

ApproachHow this behavesBest useCaution
Regular function callbackDetermined by the callerWhen the API intentionally supplies thisDoes not preserve the instance automatically
Arrow function callbackUses surrounding lexical thisShort callbacks that need the surrounding instanceCannot use an API-provided callback this
function () {}.bind(this)Fixed to the object passed to bindConverting an existing regular callbackEach bind call creates a new function
Bound prototype methodFixed once in the constructorReusable handlers and listener removalRequires one assignment per instance
call / applyChosen for one immediate invocationInvoking a function directly with a receiverNot a persistent callback solution

bind versus call:

function show() {
  console.log(this.data);
}

var item = { data: "foo" };

show.call(item);          // Invokes now; logs "foo".
var boundShow = show.bind(item); // Creates a function for later.
boundShow();              // Logs "foo".
Take Quiz

Cheat Sheet

// Arrow callback: captures surrounding this
api.on("event", () => {
  console.log(this.data);
});

// Bind an inline regular callback
api.on("event", function () {
  console.log(this.data);
}.bind(this));

// Bind a reusable method once
function Thing(api) {
  this.data = "foo";
  this.onEvent = this.onEvent.bind(this);
  api.on("event", this.onEvent);
}

Thing.prototype.onEvent = function () {
  console.log(this.data);
};
  • this depends on the call site for regular functions.
  • obj.method() gives method a this value of obj.
  • Passing obj.method as a callback does not preserve obj.
  • Arrow functions capture the surrounding this.
  • bind(object) permanently fixes a function's this.
  • Store bound handlers when you must later unregister them.
  • Use a regular callback when an API's callback this is meaningful.

FAQ

Why is this undefined in my callback?

A callback invoked as a plain function has no object receiver. In strict mode, this is undefined for that call. Bind the function or use an arrow callback when you need the surrounding instance.

Does passing this.method preserve this?

No. It passes only the function value. The later caller determines this unless the method was bound first.

Should I use an arrow function or bind?

Use an arrow for small inline callbacks. Bind a named method when the handler is reusable, testable, or must be removed from an event API later.

Can I use var self = this instead?

Yes, this older pattern works:

var self = this;
setTimeout(function () {
  console.log(self.data);
}, 1000);

Modern JavaScript usually prefers arrow functions or bind.

Why does obj.method() work but setTimeout(obj.method, 0) fail?

In obj.method(), the call is made through obj. In setTimeout(obj.method, 0), the timer later calls the function without obj.method() syntax.

Can an arrow function be rebound with bind?

No. Arrow functions ignore bind, call, and apply attempts to change their this, because their this is lexical.

Why should I store a bound event handler?

Event removal generally requires exactly the same function object used during registration. A new .bind(this) call creates a different function object.

Take Quiz

Mini Project

Description

Build a small MessageCollector object that listens to a transport's asynchronous data events. The project demonstrates how a bound instance method can update object state safely and can later be removed as a listener.

Goal

Create a collector that receives messages, stores them in its own array, prints them, and stops listening when requested.

Requirements

Create a mock transport with on, off, and emit methods. Create a MessageCollector constructor with a messages array. Register a callback that can access the collector instance through this. Store each received message and print the current list. Provide a method that unregisters the same callback.

Take Quiz

Keep learning

Related questions

View more