Topics
Languages & Runtimes

this Binding in JavaScript

The four call-site rules that decide what this is, why arrow functions ignore them, how methods lose this when passed around, and how to get it back.

Intermediate·12 min read·Updated Sep 30, 2026

Unlike every other name in a function, this is not resolved by where the function was written but by how it was called. Four call forms cover everything: called with new, called through call/apply/bind, called as a method on an object, or called plainly. Arrow functions are the one exception: they have no this of their own and read the enclosing scope's, like any other variable. Every "lost this" bug is a function that was written as a method and then called some other way.

Context

In Java and C++ this always means "the instance this method belongs to", because a method cannot exist apart from its class. JavaScript (1995) made functions first-class values that can be attached to any object, detached again and passed around, so the same function can be invoked as a method of one object, as a callback with no object, or as a constructor. this had to be decided per call. In the original sloppy mode a plain call got the global object, which turned typos into global variables; ES5 strict mode (2009) changed that to undefined and added Function.prototype.bind. ES2015 added arrow functions, which fixed the callback case by not having a this at all, and classes; ES2022 class fields made handleClick = () => {…} the idiomatic way to keep a method bound.

You have met the problem as TypeError: Cannot read properties of undefined (reading 'setState') in a React class component, as this.handleClick = this.handleClick.bind(this) in a constructor, as $(this) in jQuery handlers, and as a Vue options-API component where this.count works in a method but not inside an arrow you wrote by habit. The simplest example is two lines:

lost.js
const user = {name: 'Ada', greet() { return 'Hi, ' + this.name }}
user.greet()            // 'Hi, Ada'   — called as a method: this = user
const f = user.greet
f()                     // TypeError in strict mode: this is undefined
                        // 'Hi, undefined' in sloppy mode: this is globalThis
Call site
The place a function is invoked, and the syntax used there. It, not the definition, decides this.
Receiver
The object before the dot in obj.method(). It becomes this for that call.
Binding
The association between this and a value for one invocation. Hard binding (bind) fixes it permanently.
Strict mode
Opt-in since ES5 with 'use strict'; always on in modules and classes. Plain calls get this = undefined instead of the global object.
Arrow function
A function with no own this, arguments, super or new.target. Reads this from the scope it was defined in.

Why it matters

Modern code avoids most this by using closures and hooks, but you still meet it everywhere the platform or a library hands you an object: DOM event handlers, Node EventEmitter listeners, Express middleware on a class, Mongoose schema methods, Vue's options API, and every class-based codebase written between 2015 and 2020. The failure is silent in sloppy mode and a confusing TypeError in strict mode, and the fix is trivial once you can say which of the four rules applied at the call site.

The four rules, in order of precedence

arrow function?defined with =>yesthis = this of the enclosing scope (lexical, cannot be changed)1 · called with new?new Fn()yesthis = the freshly created object (even if Fn was bound)2 · call / apply / bind?fn.call(obj), fn.bind(obj)()yesthis = the object you passed (explicit binding)3 · called as obj.fn()?something before the dotyesthis = obj, the receiver (implicit binding)4 · plain call fn()this = undefined (strict) or globalThis (sloppy)
Decide this by looking at the call site, top to bottom. Arrow functions skip the whole tree.
rules.js
'use strict'
function show() { return this?.tag ?? this }

const a = {tag: 'a', show}
const b = {tag: 'b'}

show()                       // undefined            rule 4: plain call
a.show()                     // 'a'                  rule 3: receiver is a
show.call(b)                 // 'b'                  rule 2: explicit
const bound = show.bind(b)
bound()                      // 'b'                  rule 2: hard-bound
a.show.call(b)               // 'b'                  explicit beats implicit
new bound()                  // the new object       rule 1 beats bind
;(0, a.show)()               // undefined            comma strips the receiver
const arrow = () => this
arrow.call(b)                // module/script this   arrows ignore call()

Losing this, and getting it back

A method loses this the moment it is separated from its object: assigned to a variable, passed as a callback, destructured. At the eventual call site there is nothing before the dot, so rule 4 applies. setTimeout, addEventListener, map, promise then and React props all call your function plainly (or with a receiver of their own choosing).

lost.js
class Counter {
  count = 0
  tick() { this.count++ ; render(this.count) }
}
const c = new Counter()

setTimeout(c.tick, 1000)               // this = undefined → TypeError
button.addEventListener('click', c.tick) // this = button → button.count++
;[1, 2, 3].forEach(c.tick)             // this = undefined → TypeError
const {tick} = c; tick()               // same
kept.js
// 1. wrap in an arrow at the call site: the arrow closes over c
setTimeout(() => c.tick(), 1000)

// 2. bind once, keep the bound function (needed to remove the listener)
const tick = c.tick.bind(c)
button.addEventListener('click', tick)
button.removeEventListener('click', tick)   // works: same reference

// 3. define the method as a class field arrow: bound per instance
class Counter {
  count = 0
  tick = () => { this.count++ ; render(this.count) }
}
setTimeout(new Counter().tick, 1000)        // fine
FixCostUse when
Arrow wrapper
at the call site
A new function per call site; clearest intentOne-off callbacks, timers, promise chains
bind in the constructorOne bound function per instance; the original stays on the prototypeYou must pass the same reference twice (add/remove listener), or support older code
Class field arrowOne function per instance, not on the prototype; cannot be overridden via super or spied on the prototypeHandlers that will always be passed around, e.g. React class components
Pass thisArg (forEach, map)Free, but only where the API offers itArray iteration helpers with a method callback

DOM handlers: which this do you want?

handlers.js
button.addEventListener('click', function () {
  this.classList.toggle('on')     // regular function: this = event.currentTarget
})

button.addEventListener('click', () => {
  this.classList.toggle('on')     // arrow: this = enclosing scope, NOT the button
})

button.addEventListener('click', e => {
  e.currentTarget.classList.toggle('on')   // explicit: works in either style
})

Arrow functions and classes

An arrow function does not have this, arguments, super or new.target; a reference to any of them is resolved lexically, exactly like a variable, through the scope chain. That makes arrows perfect for callbacks inside methods and wrong for anything that is supposed to receive this from a caller.

wrong-arrow.js
const timer = {
  seconds: 0,
  start: () => {
    // arrow as a METHOD: this is the module's this (undefined), not timer
    setInterval(() => this.seconds++, 1000)   // TypeError
  },
}

class Api {
  base = '/v1'
  static create = () => new this()  // ok: static field arrow, this = Api
}
Api.prototype.get = () => this.base  // wrong: arrows have no per-call this
right-arrow.js
const timer = {
  seconds: 0,
  start() {                                   // method: this = timer
    setInterval(() => this.seconds++, 1000)   // arrow inherits it
  },
}

// the pre-2015 version of the same idea, still in older codebases:
// start: function () { var self = this; setInterval(function () {
//   self.seconds++ }, 1000) }

class Api {
  base = '/v1'
  get(path) { return fetch(this.base + path) }   // regular method
}
WhereRegular function thisArrow function this
Object literal propertyThe object, when called as obj.fn()The this of the surrounding code (usually undefined in a module)
Class method vs
class field arrow
Depends on the call site; shared on the prototypeAlways the instance; one copy per instance
Callback inside a methodundefined / the caller’s choiceThe method’s this: the common reason to use an arrow
Top level of a modulen/aundefined. In a classic script it is window; in CommonJS it is module.exports.
Static method /
static field
The class (constructor function)The class

call, apply and bind

call and apply invoke immediately with a chosen this (arguments listed versus in an array); bind returns a new function with this and optionally some leading arguments fixed. A bound function cannot be re-bound, and only new overrides it. Implementing bind is a common exercise because it uses all four rules at once.

myBind.js
Function.prototype.myBind = function (thisArg, ...preset) {
  const target = this                              // the function being bound
  function bound(...args) {
    // called with new? then ignore thisArg (rule 1 beats rule 2)
    const receiver = new.target ? this : thisArg
    return target.apply(receiver, [...preset, ...args])
  }
  bound.prototype = Object.create(target.prototype) // so new bound() works
  return bound
}

const greet = function (greeting, punct) {
  return greeting + ', ' + this.name + punct
}
const hiAda = greet.myBind({name: 'Ada'}, 'Hi')
hiAda('!')                       // 'Hi, Ada!'
hiAda.call({name: 'Bob'}, '?')   // 'Hi, Ada?'  — cannot be re-bound

// method borrowing, the classic use of call/apply:
Array.prototype.slice.call(arguments)     // pre-ES6 arguments → array
Math.max.apply(null, [3, 9, 2])           // pre-spread: Math.max(...arr)

Pitfalls

  • Arrow functions as object-literal or prototype methods

    The arrow closes over the this of the code that built the object, which is not the object. In a module that is undefined, so this.x throws. Methods that need the receiver must be regular functions or method shorthand; use arrows for the callbacks inside them.

  • Binding a new function on every render or every add

    onClick={this.handle.bind(this)} and addEventListener('click', this.handle.bind(this)) create a fresh function each time, so a later removeEventListener with another bind call never matches, and memoised children re-render. Bind once, in the constructor or as a class field.

  • Class field arrows everywhere

    They allocate one function per instance instead of one on the prototype, cannot be called through super, and are invisible to code that spies on or patches the prototype (test mocks, decorators). Use them for handlers that get passed around; keep ordinary methods as methods.

  • Relying on sloppy-mode globalThis

    Code that "worked" because a plain call got the global object, and wrote this.cache = … onto window, breaks the moment it is moved into a module or a class, where strict mode is automatic. Treat plain-call this as always undefined.

  • Expecting a closure to carry this

    Only arrows capture this lexically. A nested function inside a method gets its own this from its own call site, which is why the var self = this idiom existed. If a callback must be a regular function (to receive the DOM element, for example), read the outer object from a variable instead.

Interview questions

Q1What determines the value of this?

The call site, checked in order: a call with new binds this to the new object; call, apply or a bound function bind it to the object given; a call through a property, obj.fn(), binds it to obj; a plain call leaves it undefined in strict mode or the global object in sloppy mode. Arrow functions are outside the rules entirely: they have no own this and use the enclosing scope's, fixed at definition.

Q2const f = obj.method; f() — what happens and why?

The function runs with this undefined (or the global object in sloppy code), so any this.property access fails or reads a global. Assigning the method to a variable copies the function reference only; the object is not part of the function. At the call f() there is no receiver, so the plain-call rule applies. Fix with obj.method(), a bound copy, or an arrow wrapper.

Q3setTimeout(this.tick, 1000) inside a class breaks. Give three fixes and their trade-offs.

Wrap it: setTimeout(() => this.tick(), 1000), simplest, allocates a closure per call. Bind it once in the constructor, this.tick = this.tick.bind(this), keeps one reference you can also remove as a listener, while the prototype method stays shared. Or declare tick as a class field arrow, which auto-binds per instance at the cost of one function per object and no prototype method to override or mock.

Q4How do arrow functions differ from regular functions with respect to this, and when is an arrow the wrong choice?

An arrow has no own this, arguments, super or new.target; it resolves this lexically from where it was written and ignores call, apply, bind and the receiver. That is what you want for a callback inside a method. It is wrong for an object-literal method, a prototype method, a constructor, or a DOM handler that relies on this being the element, because in all of those the caller is supposed to supply this.

Q5Implement Function.prototype.bind.

Return a function that captures the target and the fixed this and preset arguments in a closure, then calls target.apply with the preset plus the new arguments. Handle the new case: if the returned function is invoked with new, use the newly created object as this instead of the bound one, and set the returned function's prototype to inherit from the target's so instanceof works. A bound function cannot be re-bound because the closure ignores any later receiver.

Q6What is this inside a DOM event handler?

For a regular function, the element the listener was registered on, the same as event.currentTarget, because the browser calls the handler with that receiver. For an arrow function it is whatever this was in the surrounding scope, which in a module is undefined. Reading event.currentTarget explicitly works with either and is clearer.

Q7What does the new operator do, step by step?

It creates an empty object whose prototype is the function's prototype property, calls the function with this bound to that object and the given arguments, and returns the object unless the function itself returned a different object. Because new supplies the receiver, it takes precedence over bind, and it cannot be used with arrow functions or with methods defined by method shorthand, which have no construct behaviour.

Key takeaways
  • this is decided at the call site, in order: new, then call/apply/bind, then the receiver before the dot, then plain call (undefined in strict mode).
  • Arrow functions have no own this; they read the enclosing scope's and ignore call, apply and bind. Use them for callbacks inside methods, never as methods.
  • A method loses this as soon as it is passed as a value. Wrap it in an arrow, bind it once, or declare it as a class field arrow.
  • Bind once and keep the reference if you need to remove the listener later; class field arrows cost one function per instance and skip the prototype.
  • In DOM handlers a regular function gets the element; reading event.currentTarget works in either style.
  • new creates the object, binds it as this, and returns it; it overrides even a bound function.

Preparing for interviews? DevRecall turns a job description into a prep plan that points at topics like this one.

Start free