JavaScript · Functions

The "this" Keyword in JavaScript — A Complete Guide to Binding

6 min read Updated 2026-06-17

Practice The this Keyword interview questions

Understanding the this keyword in JavaScript

this confuses more developers than any other part of JavaScript, because it doesn't behave like a normal variable. The value of this isn't fixed where a function is written — it's decided by how the function is called. Master the handful of rules that govern it and the bugs (lost context, undefined in callbacks, surprising globals) stop being mysterious. This guide covers the binding rules, arrow functions, call/apply/bind, and the classic pitfalls.

The four binding rules

For a regular function, this is set by the call site according to four rules, in order of precedence:

  1. new bindingnew Fn() -> this is the brand-new instance.
  2. Explicit bindingfn.call(obj), fn.apply(obj), fn.bind(obj) -> the object you pass.
  3. Implicit bindingobj.fn() -> the object left of the dot.
  4. Default binding — a plain fn() -> undefined in strict mode, or the global object in sloppy mode.
function who() { return this }
const obj = { who }
obj.who()       // obj      (implicit)
who.call('hi')  // 'hi'     (explicit)
new who()       // {}       (new instance)
who()           // undefined in strict mode

When several could apply, precedence decides: new beats bind beats implicit beats default.

Arrow functions ignore all of that

Arrow functions don't have their own this. They capture it lexically — from the enclosing scope at definition time — and it can never be reassigned, not even by call/apply/bind. This makes them ideal for callbacks that need the surrounding this.

class Timer {
  seconds = 0
  start() {
    setInterval(() => this.seconds++, 1000) // arrow keeps `this` = the Timer
  }
}

The flip side: arrows are a poor choice for object methods that reference the object, because this points to the outer scope, not the object — and they can't be constructors.

const obj = {
  name: 'Ada',
  greet: () => `Hi, ${this.name}`,   // this is the outer scope -> undefined
  greet2() { return `Hi, ${this.name}` }, // method shorthand -> 'Ada'
}

Arrows also lack their own arguments, super, and new.target.

Losing this: the most common bug

Because this is set at call time, passing a method somewhere else detaches it from its object. The function is copied without the obj. context, so when it's later called plainly, this is undefined/global.

const user = { name: 'Ada', greet() { return this.name } }
const fn = user.greet
fn()                       // undefined — detached
setTimeout(user.greet, 0)  // also detached

The same happens with destructuring (const { greet } = user). Fixes: an arrow wrapper that calls the method on the object, or bind:

setTimeout(() => user.greet(), 0)      // called ON user
setTimeout(user.greet.bind(user), 0)   // permanently bound

This is exactly why React class components needed this.handleClick = this.handleClick.bind(this) in the constructor, or arrow class fields.

call, apply, and bind

All three set this explicitly; they differ in invocation and arguments:

  • fn.call(thisArg, a, b) — invoke now, args listed.
  • fn.apply(thisArg, [a, b]) — invoke now, args as an array.
  • fn.bind(thisArg, a) — return a new function with this (and any given args) permanently locked in.
function greet(g, mark) { return `${g}, ${this.name}${mark}` }
const user = { name: 'Ada' }
greet.call(user, 'Hi', '!')     // 'Hi, Ada!'
greet.apply(user, ['Hi', '!'])  // 'Hi, Ada!'
const greetAda = greet.bind(user)
greetAda('Hey', '.')            // 'Hey, Ada.'

bind also enables partial application (pre-fill leading args with a null thisArg). Once bound, this is fixed — a later call/apply/bind can't change it. But new does override a bound this (the new instance wins, though bound arguments are kept). For just forwarding an array of arguments without caring about this, modern code uses the spread operator (Math.max(...nums)) instead of apply.

this in different contexts

  • Methods (shorthand or prototype): this is the object the method is called on.
  • Constructors / new: a fresh object is created and this points to it; it's returned automatically unless the constructor returns its own object. Forgetting new triggers default binding and leaks globals.
  • Classes: bodies are always strict, so an extracted method called plainly has this === undefined (a TypeError, not the global object). Arrow class fields bind this to the instance.
  • Static methods: this is the class itself.
  • Event listeners: a regular-function handler's this is the element; an arrow's is the surrounding scope.
  • Top level: in a classic script (sloppy) this is the global object; in an ES module it's undefined. Use globalThis to reliably reference the global object anywhere.
class Counter {
  count = 0
  inc() { this.count++ }
  incArrow = () => this.count++ // bound to the instance
}

Strict mode and method chaining

Strict mode makes a plain call's this undefined instead of the global object, surfacing "I forgot to bind this" bugs immediately (a loud TypeError rather than silent global leakage). ES modules and classes are always strict. A useful pattern that relies on implicit binding: returning this from methods to enable fluent chaining:

class Query {
  parts = []
  where(c) { this.parts.push(c); return this }
  order(o) { this.parts.push(o); return this }
}
new Query().where('a=1').order('b') // each call returns the same instance

Recap

this is determined by the call site, not the definition site, following four rules: new, explicit (call/apply/bind), implicit (obj.method()), and default (undefined/global). Arrow functions opt out and inherit this lexically — great for callbacks, wrong for object methods. Passing a method as a callback loses its receiver; fix it with an arrow wrapper or bind. Class bodies and modules are strict, so detached methods get undefined. Internalize the rules and this becomes predictable instead of magical.

Practice tests are coming soon

Get notified when interactive mock interviews and quizzes launch.