The this keyword in JavaScript
this is a reference to the object that is currently invoking the function. The value is determined at call time, not at definition time.
Theory
TL;DR
thisis like a "who's calling?" display: it shows the caller, not the function itself- Regular functions get
thisfrom the call site; arrow functions lock it from the scope where they were written obj.method()setsthistoobj; passing that method as a callback loses the binding- Regular functions for object methods, arrows for callbacks and nested functions
call,apply, andbindlet you setthisexplicitly
Quick example
const obj = {
name: 'Alice',
regular: function() { console.log(this.name); }, // this = obj
arrow: () => console.log(this.name) // this = outer scope
};
obj.regular(); // "Alice"
obj.arrow(); // undefined (strict mode)Two functions, same object, opposite results. The call obj.regular() binds this to obj. The arrow function ignores that completely. It captured this from the surrounding scope at the moment it was defined, and nothing changes that.
Key difference
Regular functions resolve this at runtime based on how they are invoked. obj.method() means this is obj; calling fn() standalone means this is the global object, or undefined in strict mode. Arrow functions skip this resolution entirely. They lock this to whatever the enclosing scope held at creation time. Passing call, apply, or bind to an arrow function has zero effect on its this.
When to use
- Object method that reads or writes instance data: regular function
- Callback for
setTimeout,forEach, or event listeners: arrow, it preserves outerthis - Constructor with
new: regular function only. Arrows throwTypeError: not a constructor - Class event handler: arrow class field (
handleClick = () => {}) removes the need for manualbindin the constructor - Standalone utility with no object context: arrow, no risk of accidentally touching global
this
How this gets resolved
JavaScript engines attach an execution context to every function call. For regular functions, the engine checks the call site: obj.fn() sets this to obj; fn() alone falls back to the global object (or undefined in strict mode). bind() creates a new function with a hardcoded this that overrides any call site. Arrow functions do not participate in this mechanism at all. They capture this from the parent scope's activation record at creation time, so no invocation can alter it.
One environment detail worth knowing: in ESM modules, the top-level this is always undefined. In CommonJS it points to module.exports. That difference surfaces when debugging Node.js utility files.
Common mistakes
Arrow function used as an object method:
const user = {
name: 'Bob',
greet: () => console.log(this.name) // wrong: arrow has no binding to user
};
user.greet(); // undefinedArrows do not have access to the object they sit inside. Use a regular function for any method that needs to read properties off its object.
Losing this in a callback:
const logger = {
prefix: '[INFO]',
log: function() {
console.log(this.prefix); // undefined, this = global inside setTimeout
}
};
setTimeout(logger.log, 0); // detached from logger
setTimeout(() => logger.log(), 0); // works: arrow wrapper calls log as a methodPassing logger.log to setTimeout extracts the function from its object. The binding is gone. The arrow wrapper fixes this by calling the method directly on the object rather than passing a bare reference. This is the mistake I see most often in code reviews, including in production Express codebases where a method reference gets handed off to a queue and fails without an obvious error.
Unbound event handler:
function Counter() {
this.count = 0;
document.querySelector('button').addEventListener('click', function() {
this.count++; // this = button element, not the Counter instance
});
}Fix: use an arrow function or call .bind(this) when attaching the listener.
Calling new on an arrow:
const Ctor = () => {};
new Ctor(); // TypeError: Ctor is not a constructorArrows have no [[Construct]] internal slot. Only regular functions can be used with new.
Real-world usage
- React class components:
this.setState()andthis.propsdepend onthispointing to the component instance. Arrow class fields (handleClick = () => {}) replaced manualbindcalls in constructors and are still common in legacy codebases - Express error handler classes: methods need
bindor arrow class fields to keepthison the instance when Express calls them - Node.js Transform streams:
this.push(chunk)inside_transformworks because Node calls it as a method on the stream object - Lodash:
_.bind(fn, context)and_.bindAll(obj, methods)exist specifically for fixingthisin callback-heavy code
Follow-up questions
Q: What is this inside setTimeout(fn, 0)?
A: The global object, or undefined in strict mode. setTimeout calls the function without an object context, so the binding is lost.
Q: What is the difference between call, apply, and bind?
A: All three set this explicitly. call and apply invoke the function immediately: call takes individual arguments, apply takes an array. bind returns a new function with this permanently set, without invoking it.
Q: What is this at the top level of an ES module?
A: Always undefined. In CommonJS modules it is module.exports.
Q: How does new affect this?
A: new creates an empty object, sets this to it inside the constructor, and returns that object unless the constructor explicitly returns a different object.
Q: Can you override this in an arrow function using call or bind?
A: No. Arrow functions ignore any this passed via call, apply, or bind. The lexical binding is permanent from the moment the function is created.
Q: (Senior) How does V8 handle this in optimized code?
A: V8 uses inline caches to track the type of this at each call site. If the type stays consistent across calls (monomorphic), TurboFan can optimize aggressively. If the type varies (polymorphic), V8 deoptimizes. This is one reason consistent object shapes and strict mode improve runtime performance.
Examples
Method call vs detached call
const car = {
speed: 60,
accelerate: function() {
this.speed += 10;
console.log(this.speed);
}
};
car.accelerate(); // 70, this = car
const fn = car.accelerate;
fn(); // NaN in strict mode (undefined + 10) or modifies global speedAssigning a method to a variable detaches it from the object. The function body is identical in both cases. Only the call site differs, and that is what changes this.
React class component with bind and arrow class field
class UserProfile extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this); // manual bind for regular method
}
handleClick() {
this.setState({ count: this.state.count + 1 }); // this = instance via bind
}
handleArrow = () => {
this.setState({ count: this.state.count + 1 }); // this = instance automatically
};
render() {
return <button onClick={this.handleArrow}>{this.state.count}</button>;
}
}Both approaches work. The arrow class field is cleaner and skips the constructor boilerplate. The bound method shares the implementation across instances via the prototype. In practice, most teams default to arrow fields. The per-instance function overhead is negligible for UI components.
Lost this in Express middleware
app.use((req, res, next) => {
const logger = {
userAgent: req.get('User-Agent'),
log: function() {
console.log(this.userAgent); // undefined, this = global inside setTimeout
}
};
setTimeout(logger.log, 0); // broken
setTimeout(() => logger.log(), 0); // works: arrow calls log as a method of logger
next();
});The arrow wrapper () => logger.log() does not pass a bare function reference. It calls the method directly on the object, keeping the binding intact. This pattern comes up frequently in async middleware and queue processors where function references get passed around.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.