Skip to main content

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

  • this is like a "who's calling?" display: it shows the caller, not the function itself
  • Regular functions get this from the call site; arrow functions lock it from the scope where they were written
  • obj.method() sets this to obj; passing that method as a callback loses the binding
  • Regular functions for object methods, arrows for callbacks and nested functions
  • call, apply, and bind let you set this explicitly

Quick example

javascript
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 outer this
  • Constructor with new: regular function only. Arrows throw TypeError: not a constructor
  • Class event handler: arrow class field (handleClick = () => {}) removes the need for manual bind in 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:

javascript
const user = { name: 'Bob', greet: () => console.log(this.name) // wrong: arrow has no binding to user }; user.greet(); // undefined

Arrows 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:

javascript
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 method

Passing 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:

javascript
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:

javascript
const Ctor = () => {}; new Ctor(); // TypeError: Ctor is not a constructor

Arrows have no [[Construct]] internal slot. Only regular functions can be used with new.

Real-world usage

  • React class components: this.setState() and this.props depend on this pointing to the component instance. Arrow class fields (handleClick = () => {}) replaced manual bind calls in constructors and are still common in legacy codebases
  • Express error handler classes: methods need bind or arrow class fields to keep this on the instance when Express calls them
  • Node.js Transform streams: this.push(chunk) inside _transform works because Node calls it as a method on the stream object
  • Lodash: _.bind(fn, context) and _.bindAll(obj, methods) exist specifically for fixing this in 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

javascript
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 speed

Assigning 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

javascript
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

javascript
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 ready
Premium

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

Finished reading?