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.
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()meansobjis holding and usingalert, sothisisobj.setTimeout(obj.alert, 1000)hands the tool to someone else. When the timer uses it,objis no longer identified as the holder.obj.alert.bind(obj)attaches a label saying: “Whenever this tool is used, treatobjas the holder.”
An arrow function behaves differently: it does not create its own this. It uses the this from the surrounding constructor function.
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.
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);
new MyConstructor("foo", transport)creates a new object.- Inside the constructor,
thisrefers to that new object. this.data = datastores"foo"on the instance.- The arrow function is created inside the constructor.
- Unlike a regular function, the arrow function does not get a new
thiswhen the timer invokes it. - After one second,
transportinvokes the callback. - The callback uses the constructor's saved
this, which isobj. this.datatherefore evaluates to .
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
setIntervalcallbacks. - 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, orcloseevents.
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.
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
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
Comparisons
| Approach | How this behaves | Best use | Caution |
|---|---|---|---|
| Regular function callback | Determined by the caller | When the API intentionally supplies this | Does not preserve the instance automatically |
| Arrow function callback | Uses surrounding lexical this | Short callbacks that need the surrounding instance | Cannot use an API-provided callback this |
function () {}.bind(this) | Fixed to the object passed to bind | Converting an existing regular callback | Each bind call creates a new function |
| Bound prototype method | Fixed once in the constructor |
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(.);
};
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 work but fail?
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.
Keep learning
Related questions
Abort Ajax Requests with jQuery jqXHR.abort()
Learn how to cancel an in-progress jQuery Ajax request with jqXHR.abort(), handle abort status safely, and avoid stale UI updates.
Add Key-Value Pairs to JavaScript Objects
Learn how to add key-value pairs to JavaScript objects with dot and bracket notation, dynamic keys, examples, and common mistakes.
Adding Table Rows with jQuery: append(), Limits, and Best Practices
Learn how to add table rows in jQuery using append(), what elements are allowed in tables, and safer ways to build rows dynamically.