JavaScript this Keyword 2026 Binding Rules Tutorial

JavaScript this Keyword: 4 Binding Rules You Must Know in 2026

[rank_math_toc]

The JavaScript this keyword is the single most confusing concept in the language — and it trips up senior developers just as often as beginners. Unlike languages like Java or C# where this always refers to the current instance, JavaScript’s this is determined by how a function is called, not where it’s defined. This one rule explains every weird behavior you’ve ever encountered with this in JavaScript.

In this lesson, we’ll dissect every context where the this keyword behaves differently, master bind(), call(), and apply(), and learn the pitfalls that cause real bugs in production code. If you’ve been writing JavaScript functions and wondering why this sometimes returns undefined, you’re about to get answers.

How the JavaScript this Keyword Works: The Core Rule

Here’s the rule that governs everything: this refers to the object that is executing the current function. That’s it. But the devil is in the details of what “executing” means in different contexts.

There are exactly four binding rules, applied in this priority order:

  1. new binding — called with new? this = the newly created object
  2. Explicit binding — called with call(), apply(), or bind()? this = the specified object
  3. Implicit binding — called as a method on an object? this = that object
  4. Default binding — plain function call? this = window (or undefined in strict mode)

this in Global Context

In the global execution context (outside any function), this refers to the global object. In browsers, that’s window. In Node.js, it’s the global object (or module.exports in module scope).

// Browser global context
console.log(this === window); // true

// Adding a property to this adds it to window
this.myGlobal = 42;
console.log(window.myGlobal); // 42

In strict mode, the default binding changes. A plain function call gives this as undefined instead of the global object. This is why modern JavaScript almost always uses strict mode — it catches accidental global variable creation.

"use strict";

function showThis() {
  console.log(this); // undefined (not window!)
}

showThis();

this in Object Methods

When a function is called as a method of an object, this refers to the object that owns the method. This is the implicit binding rule and the most intuitive behavior.

const user = {
  name: "Chirag",
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

user.greet(); // "Hello, I'm Chirag"
// this === user because user is the calling object

But here’s where it gets dangerous. If you extract the method from the object, this loses its binding:

const user = {
  name: "Chirag",
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

const greetFn = user.greet; // Extracting the method
greetFn(); // "Hello, I'm undefined" (or error in strict mode)
// this === window (or undefined in strict mode)

This is the number one this bug in JavaScript. It happens when you pass methods as callbacks, assign them to variables, or use them in event handlers.

this in Arrow Functions vs Regular Functions

Arrow functions are the exception to every rule above. They don’t have their own this — instead, they inherit this from the enclosing lexical scope. This is called lexical this.

const user = {
  name: "Chirag",
  hobbies: ["coding", "security", "coffee"],

  // Regular function: this === user
  showHobbiesRegular() {
    this.hobbies.forEach(function(hobby) {
      // BUG: this !== user here. It's window/undefined
      console.log(`${this.name} likes ${hobby}`);
    });
  },

  // Arrow function: this is inherited from showHobbiesArrow
  showHobbiesArrow() {
    this.hobbies.forEach((hobby) => {
      // WORKS: this === user (inherited from outer method)
      console.log(`${this.name} likes ${hobby}`);
    });
  }
};

user.showHobbiesArrow();
// "Chirag likes coding"
// "Chirag likes security"
// "Chirag likes coffee"

This is why arrow functions were invented — they solve the this problem in callbacks. Before ES6, developers used var self = this; or .bind(this) as workarounds.

Critical rule: Never use arrow functions as object methods. Since arrow functions don’t have their own this, using them as methods means this won’t refer to the object:

const user = {
  name: "Chirag",
  // DON'T do this
  greet: () => {
    console.log(this.name); // undefined — this is window/module
  }
};

bind(), call(), and apply() in JavaScript

These three methods let you explicitly set what this refers to. Understanding bind, call, and apply in JavaScript is essential for any serious developer.

call() — Invoke with a specific this

call() invokes the function immediately with the first argument as this, and remaining arguments passed individually.

function introduce(greeting, punctuation) {
  console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}

const person = { name: "Chirag" };

introduce.call(person, "Hey", "!"); // "Hey, I'm Chirag!"

apply() — Same as call, but with an array

apply() is identical to call() except arguments are passed as an array. This was crucial before the spread operator existed.

introduce.apply(person, ["Hello", "."]); // "Hello, I'm Chirag."

// Classic use case: finding max in an array (before spread)
const numbers = [5, 2, 8, 1, 9];
Math.max.apply(null, numbers); // 9

// Modern equivalent with spread
Math.max(...numbers); // 9

bind() — Create a new function with fixed this

bind() doesn’t call the function. It returns a new function with this permanently set. This is the go-to solution for callback this problems.

const user = {
  name: "Chirag",
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

const boundGreet = user.greet.bind(user);

// Now it works even when called without the object
boundGreet(); // "Hello, I'm Chirag"

// Works as a callback too
setTimeout(boundGreet, 1000); // "Hello, I'm Chirag" (after 1 second)

Important: Once a function is bound, you can’t re-bind it. The first bind() wins.

const anotherPerson = { name: "Alice" };
const reBound = boundGreet.bind(anotherPerson);
reBound(); // Still "Hello, I'm Chirag" — first bind wins

this in Event Handlers and setTimeout

Event handlers set this to the DOM element that fired the event. This is a source of countless bugs when using class methods as handlers.

const button = document.querySelector("#myBtn");

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

// Arrow function: this is NOT the button
button.addEventListener("click", () => {
  console.log(this); // window (or enclosing scope)
});

setTimeout and setInterval call their callbacks as plain functions, so this defaults to window (or undefined in strict mode):

const timer = {
  seconds: 0,

  // BUG: this is window/undefined inside setTimeout callback
  startBroken() {
    setInterval(function() {
      this.seconds++; // TypeError or NaN
      console.log(this.seconds);
    }, 1000);
  },

  // FIX 1: Arrow function
  startFixed() {
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  },

  // FIX 2: bind
  startBound() {
    setInterval(function() {
      this.seconds++;
      console.log(this.seconds);
    }.bind(this), 1000);
  }
};

this in Classes

In ES6 classes, this in methods refers to the instance — but only when called as a method. The same extraction bug applies:

class Counter {
  count = 0;

  increment() {
    this.count++;
    console.log(this.count);
  }
}

const counter = new Counter();
counter.increment(); // 1 — works fine

const inc = counter.increment;
inc(); // TypeError: Cannot read property 'count' of undefined

// Fix: Use class field with arrow function
class BetterCounter {
  count = 0;

  // Arrow function as class field — this is always the instance
  increment = () => {
    this.count++;
    console.log(this.count);
  };
}

const better = new BetterCounter();
const betterInc = better.increment;
betterInc(); // 1 — works because of lexical this

React developers use this pattern constantly — class fields with arrow functions are how React class components handle event binding. Though with React hooks, this problem largely disappears.

this in forEach, map, and Other Array Methods

Array methods like forEach(), map(), and filter() accept an optional thisArg parameter. Most developers don’t know this exists:

const processor = {
  prefix: "PROCESSED",

  process(items) {
    return items.map(function(item) {
      return `${this.prefix}: ${item}`;
    }, this); // Second argument sets this inside the callback
  }
};

processor.process(["a", "b", "c"]);
// ["PROCESSED: a", "PROCESSED: b", "PROCESSED: c"]

// But honestly, just use an arrow function instead:
const betterProcessor = {
  prefix: "PROCESSED",
  process(items) {
    return items.map(item => `${this.prefix}: ${item}`);
  }
};

Common this Pitfalls and Interview Questions

Pitfall 1: Nested functions lose this

const obj = {
  value: 42,
  getValue() {
    function inner() {
      return this.value; // undefined — inner is a plain function call
    }
    return inner();
  }
};

// Fix: use arrow function for inner, or const self = this;

Pitfall 2: Destructuring methods from objects

const { greet } = user; // greet loses its this binding
greet(); // undefined

Classic Interview Question: What does this log?

const obj = {
  name: "outer",
  inner: {
    name: "inner",
    getName() {
      return this.name;
    }
  }
};

console.log(obj.inner.getName()); // "inner" — this = obj.inner
const fn = obj.inner.getName;
console.log(fn()); // undefined — this = window/undefined

Quick Reference: this Binding Cheat Sheet

Context this Value Example
Global scope window / global console.log(this)
Object method The object obj.method()
Plain function window / undefined func()
Arrow function Inherited from enclosing scope () => this
new keyword New instance new Func()
call/apply/bind Specified object func.call(obj)
Event handler The DOM element el.onclick = func
Class method The instance instance.method()

When to Use bind() vs Arrow Functions

Modern JavaScript gives you two tools to fix this problems. Here’s when to use each:

  • Arrow functions: Use when you need lexical this in callbacks, event handlers inside classes, or any inline function that needs the parent’s this.
  • bind(): Use when you have an existing named function and need to pass it somewhere with a fixed this. Also useful for partial application (pre-filling arguments).
// Partial application with bind
function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2);
console.log(double(5)); // 10
console.log(double(10)); // 20

Summary

The JavaScript this keyword follows one core principle: it’s determined by the call site, not the definition site. Master the four binding rules (new, explicit, implicit, default), understand that arrow functions inherit this lexically, and you’ll never be confused by this again. In the next lessons, we’ll dive into JavaScript arrays and objects where this binding comes up constantly in method chaining and iteration.

Further reading: MDN — this | JavaScript.info — Object methods and this | web.dev — JavaScript this | You Don’t Know JS: this & Object Prototypes

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *