Updated June 2026. Tested on modern JavaScript (ES2020+). Part of the Techalyst JavaScript series.

this is the part of JavaScript that quietly breaks more code than any other, and arrow functions are the tool that finally tamed it. The two are inseparable: you cannot understand arrow functions without understanding this, and once you do, a whole class of bugs disappears. Here is the working model.

What this points to

For a regular function, this is decided by how the function is called, not where it is written. Called as an object method, this is the object. Called bare, this falls back to the global object (or undefined in strict mode).

const user = {
  name: 'Aisha',
  greet: function () {
    console.log(this.name)
  },
}

user.greet() // 'Aisha', called as a method, this is user

That "decided by how it is called" rule is the source of the trouble, because the same function can have a different this depending on the call site.

The context-loss bug

Pull a method off its object and call it on its own, and this is lost.

const greet = user.greet
greet() // undefined, no object on the left of the call

Nothing about the function changed; only the call site did. This is the single most common this bug, and it shows up everywhere: passing a method as a callback, an event handler, or a setTimeout.

Arrow functions inherit this

An arrow function does not get its own this. Instead it permanently uses the this of the scope it was defined in. It does not matter how or where you later call it.

function Timer() {
  this.seconds = 0
  setInterval(() => {
    this.seconds++ // this is the Timer instance, inherited from the enclosing scope
  }, 1000)
}

Had that callback been a regular function, its this would have been the global object and this.seconds would fail. The arrow inherits this from Timer, so it just works. This is exactly why arrows are the right choice for callbacks nested inside methods.

The flip side: an arrow is the wrong choice for an object method, because it inherits the outer (usually global) this instead of the object.

const counter = {
  count: 0,
  increment: () => {
    this.count++ // this is NOT counter; arrows do not bind to the object
  },
}

Rule of thumb: regular function for a method, arrow function for a callback inside it.

The old workaround

Before arrows, people captured this in a variable to smuggle it into a nested function.

function Timer() {
  const self = this // capture it
  setInterval(function () {
    self.seconds++ // use the captured reference
  }, 1000)
}

You still see const self = this (or const that = this) in older code. Arrow functions make it unnecessary, but recognising the pattern helps when reading legacy code.

Redirecting this with bind, call and apply

When you do need to set this on a regular function explicitly, three methods do it. They only work on regular functions, arrows ignore them entirely, since arrows have no own this to set.

  • bind returns a new function with this permanently fixed, it does not call it.
  • call invokes immediately, with arguments listed individually.
  • apply invokes immediately, with arguments passed as an array.
const greet = user.greet

const bound = greet.bind(user)
bound() // 'Aisha', this is locked to user

greet.call(user)  // 'Aisha', runs now
greet.apply(user) // 'Aisha', runs now, array args

bind is the usual fix for the context-loss bug: bind a method to its object before handing it off as a callback.

Method Calls now? Arguments Returns
bind No pre-fills, optional a new bound function
call Yes comma-separated the return value
apply Yes array the return value

A few other arrow differences

Beyond this, arrows differ from regular functions in ways worth knowing. They have no arguments object (use rest parameters ...args instead), they cannot be used as constructors with new, and a single-expression body returns implicitly with no return keyword. To return an object literal from that short form, wrap it in parentheses so it is not mistaken for a function body.

const double = (x) => x * 2              // implicit return
const make = (name) => ({ name })        // parens to return an object
const sum = (...nums) => nums.reduce((a, b) => a + b, 0) // rest, not arguments

Wrapping up

this in a regular function is set by the call site, which is why extracting a method loses its context. Arrow functions sidestep the whole problem by inheriting this from where they are defined, making them ideal for callbacks and wrong for object methods. When you need explicit control on a regular function, reach for bind, call, or apply. Internalise "method gets a regular function, callback gets an arrow" and most this bugs never happen.

More in the series: closures and the factory pattern and scope, hoisting and references. Questions welcome below.