Back to JavaScript and TypeScript

The this Keyword

this is determined at call time, not definition time except in arrow functions. Four rules cover every case. Once you know which rule applies, this stops being confusing.

Core Rules

The Four Binding Rules

Every regular function call falls into one of these four categories. Check the call site not the definition site to determine which rule applies.

Default binding standalone call

When a function is called without any context, this is the global object in non-strict mode (window in browsers, global in Node.js) or undefined in strict mode.

function showThis() {
  console.log(this)
}

showThis()  // global object (sloppy) or undefined (strict)

// In strict mode:
"use strict"
function strictShowThis() {
  console.log(this)  // undefined
}
strictShowThis()

Implicit binding method call

When a function is called as a method of an object, this is the object to the left of the dot. The binding happens at the call site, not the definition site.

const user = {
  name: "Alice",
  greet() {
    return `Hello, I'm ${this.name}`
  }
}

console.log(user.greet())  // "Hello, I'm Alice"

// The trap: assigning the method to a variable loses the binding
const fn = user.greet
console.log(fn())  // "Hello, I'm undefined"   this is now global

Explicit binding call, apply, bind

call and apply invoke a function immediately with a specific this. bind returns a new function permanently tied to that this useful for callbacks.

function greet(greeting, punctuation) {
  return `${greeting}, ${this.name}${punctuation}`
}

const person = { name: "Bob" }

// call  args as a list
greet.call(person, "Hi", "!")     // "Hi, Bob!"

// apply  args as an array
greet.apply(person, ["Hey", "."]) // "Hey, Bob."

// bind  returns a new function
const boundGreet = greet.bind(person, "Hello")
boundGreet("?")  // "Hello, Bob?"

new binding constructor call

When a function is invoked with new, a fresh object is created and this inside the function refers to that new object.

function Car(model) {
  this.model = model
  this.drive = function() { return `${this.model} is driving` }
}

const tesla = new Car("Tesla")
console.log(tesla.drive())   // "Tesla is driving"
console.log(tesla.model)     // "Tesla"

Arrow Functions

Arrow Functions

Arrow functions solve the most common this pain point: callbacks that lose context. They simply don't have their own this they look it up in the enclosing scope at the time they're written.

Arrow functions have no own this

Arrow functions do not have their own this. They inherit this from the surrounding lexical scope at definition time. No call-site rule can change it.

const timer = {
  seconds: 0,
  start() {
    // Regular function  this would be wrong inside setInterval
    setInterval(() => {
      this.seconds++           // this = timer (lexical scope)
      console.log(this.seconds)
    }, 1000)
  }
}

timer.start()  // 1, 2, 3 ...

// If we used a regular function instead:
setInterval(function() {
  this.seconds++  // this = global  broken
}, 1000)

Arrow functions in class methods

Class fields defined as arrow functions capture this at construction time, making them safe to pass as callbacks. The tradeoff: each instance gets its own copy instead of sharing via the prototype.

class Button {
  label = "Click me"

  // Arrow  this is always the Button instance
  handleClick = () => {
    console.log(`${this.label} was clicked`)
  }

  // Regular method  this depends on how it's called
  regularClick() {
    console.log(`${this.label} was clicked`)
  }
}

const btn = new Button()
document.addEventListener("click", btn.handleClick)    // works
document.addEventListener("click", btn.regularClick)   // this = undefined

Reference

Binding Priority

When multiple rules could apply, a clear precedence order decides. Arrow functions are outside the system entirely.

// Binding priority (highest to lowest):
// 1. new binding          new Foo()
// 2. Explicit binding     fn.call(ctx) / fn.bind(ctx)()
// 3. Implicit binding     obj.fn()
// 4. Default binding      fn()

// Arrow functions ignore all four  they always use lexical this.