Reference

JavaScript this Keyword Explained: 4 Binding Rules & Arrow Functions

Master JavaScript's this keyword with the 4 binding rules in resolution order, arrow function behavior, and fixes for common callback and setTimeout bugs.

JavaScript this Keyword Explained

The value of this in JavaScript is not determined by where a function is defined, but by how it is called. Every regular function invocation resolves this by walking through four binding rules in a fixed priority order. Arrow functions break the rules entirely by inheriting this from their enclosing lexical scope.

The 4 Binding Rules (in Resolution Order)

1. new Binding (Highest Priority)

When a function is invoked with the new keyword, JavaScript creates a fresh object and binds this to it.

function User(name) {
  this.name = name; // this = the new object
}
const u = new User("Ada");
console.log(u.name); // "Ada"

This rule beats every other rule, even bind().

2. Explicit Binding

call(), apply(), and bind() force this to a specific value.

function greet() { return `Hi, ${this.name}`; }
const person = { name: "Ren" };

greet.call(person);          // "Hi, Ren"
greet.apply(person);         // "Hi, Ren"
const bound = greet.bind(person);
bound();                     // "Hi, Ren"

call and apply invoke immediately; bind returns a new function with this permanently locked. A bound function cannot be re-bound.

3. Implicit Binding

When a function is called as a method of an object, this is the object to the left of the dot.

const obj = {
  name: "Kai",
  greet() { return this.name; }
};
obj.greet(); // "Kai"  — this = obj

Only the final reference matters. In a.b.c.method(), this is c, not a.

4. Default Binding (Lowest Priority)

If none of the above apply, this falls back to a default:

  • Strict mode: this is undefined.
  • Sloppy mode: this is the global object (window in browsers, global in Node).
"use strict";
function show() { console.log(this); }
show(); // undefined

ES modules and class bodies are strict by default.

Arrow Functions: No Own this

Arrow functions do not have their own this. They capture this lexically from the surrounding scope at the moment of definition. The four rules above do not apply: call, apply, bind, and even new cannot change an arrow's this.

const obj = {
  name: "Zoe",
  regular() { return this.name; },     // this = obj
  arrow: () => this.name              // this = enclosing scope (not obj!)
};
obj.regular(); // "Zoe"
obj.arrow();   // undefined (or global.name)

Arrows also cannot be used with new — they throw TypeError.

Common Bugs and Fixes

Losing this in Callbacks

class Counter {
  constructor() { this.count = 0; }
  increment() { this.count++; }
}
const c = new Counter();
[1, 2, 3].forEach(c.increment); // TypeError: this is undefined

The method is extracted from the object and called plainly, so implicit binding is lost. Three fixes:

// Fix A: bind
[1,2,3].forEach(c.increment.bind(c));

// Fix B: arrow wrapper (captures outer this)
[1,2,3].forEach(() => c.increment());

// Fix C: pass thisArg (forEach supports it)
[1,2,3].forEach(c.increment, c);

setTimeout Losing Context

class Timer {
  constructor() { this.seconds = 0; }
  start() {
    setTimeout(function () {
      this.seconds++;             // this = undefined / window
    }, 1000);
  }
}

Fix by using an arrow, which inherits this from start():

start() {
  setTimeout(() => { this.seconds++; }, 1000);
}

Event Handlers

DOM event handlers set this to the element that fired the event — unless you attach an arrow, which keeps the surrounding this.

button.addEventListener("click", function () {
  console.log(this); // the button element
});

button.addEventListener("click", () => {
  console.log(this); // enclosing scope's this
});

Method Extraction

const q = document.querySelector;  // extracted
q("div");                          // TypeError: Illegal invocation
// DOM methods enforce a specific this-binding, and console.log
// happens NOT to require one — but querySelector, addEventListener,
// and many Element methods do. Fix with .bind or an arrow wrapper:
const q2 = document.querySelector.bind(document);
const q3 = (s) => document.querySelector(s);

Re-bind: const log = console.log.bind(console);

Class Methods and this

Class bodies are strict mode. Prototype methods rely on implicit binding, so extracting them loses this.

class Button {
  constructor(label) { this.label = label; }
  // Prototype method — this depends on call site
  click() { console.log(this.label); }

  // Public class field arrow — this is bound to instance forever
  clickBound = () => { console.log(this.label); };
}

const b = new Button("Save");
const f1 = b.click;       f1();      // undefined / crash
const f2 = b.clickBound;  f2();      // "Save"

Class-field arrows are the modern replacement for constructor-based this.method = this.method.bind(this) patterns (common in React class components).

Quick Resolution Cheat Sheet

  1. Called with new? → new object.
  2. Called with call / apply / bound? → the specified object.
  3. Called as obj.fn()? → obj.
  4. Otherwise? → undefined (strict) or global (sloppy).
  5. Arrow function? → ignore 1–4; use enclosing lexical this.

When debugging, ask "how is this function being called right now?" — never "where was it written?". That single mental shift resolves the vast majority of this bugs.

javascript this this binding arrow functions call apply bind lexical this implicit binding strict mode this class methods

Explore 300+ Free Tools

Utilko has tools for developers, writers, designers, students, and everyday users — all free, all browser-based.