DocsHub
Advanced

The this Keyword

Learn how this works in JavaScript, why it can be confusing, and how to control it with call, apply, and bind.

The this Keyword

this is one of the most confusing things in JavaScript. In most languages this always refers to the current object. In JavaScript — this depends entirely on how and where a function is called.

The same function can have a different this every time it is called. Understanding exactly how this is determined removes all the confusion.


The Core Rule

this is not determined when a function is defined. It is determined when a function is called.

There are four rules that determine what this is — and they apply in this order of priority:

yes no yes no yes no How is the function called? new keyword? this = new object call or apply or bind? this = explicitly set object method on an object? this = the object this = global or undefined in strict mode

Rule 1 — Default Binding

When a function is called on its own — not as a method, not with newthis is the global object (window in browsers, global in Node.js). In strict mode it is undefined.

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

showThis(); // window (browser) or global (Node.js)
"use strict";

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

showThis(); // undefined — strict mode

Default binding is the fallback when no other rule applies. In modern JavaScript with modules — which always run in strict mode — default binding gives undefined, not the global object.


Rule 2 — Implicit Binding

When a function is called as a method of an object — object.method()this is that object.

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

user.greet(); // Hello, I am Ali — this is user

The object to the left of the dot at the time of the call is this.

Implicit binding can be lost

This is the most common this bug. When you take a method out of an object and call it separately, the binding is lost.

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

const greetFn = user.greet; // copy the function — not the binding
greetFn(); // Hello, I am undefined — this is global or undefined

The function is the same — but now it is called without an object to the left of the dot. Default binding applies.

// Same issue with callbacks
const user = {
  name: "Ali",
  greet() {
    console.log(`Hello, I am ${this.name}`);
  }
};

setTimeout(user.greet, 1000);
// Hello, I am undefined — setTimeout calls greet without the user context

Rule 3 — Explicit Binding

Use call(), apply(), or bind() to explicitly set what this should be.

call() — call with explicit this

function greet(greeting, punctuation) {
  console.log(`${greeting}, I am ${this.name}${punctuation}`);
}

const ali = { name: "Ali" };
const sara = { name: "Sara" };

greet.call(ali, "Hello", "!");   // Hello, I am Ali!
greet.call(sara, "Hi", ".");     // Hi, I am Sara.

call(thisArg, arg1, arg2, ...) — first argument is this, rest are the function arguments.

apply() — same as call but arguments as array

greet.apply(ali, ["Hello", "!"]);  // Hello, I am Ali!
greet.apply(sara, ["Hi", "."]);    // Hi, I am Sara.

apply(thisArg, [arg1, arg2, ...]) — arguments passed as an array. Useful when you already have arguments in an array.

const numbers = [5, 3, 8, 1, 9, 2];

// Math.max does not accept an array — apply solves this
console.log(Math.max.apply(null, numbers)); // 9

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

bind() — create a new function with fixed this

bind() does not call the function immediately. It returns a new function with this permanently bound.

function greet(greeting) {
  console.log(`${greeting}, I am ${this.name}`);
}

const ali = { name: "Ali" };

const aliGreet = greet.bind(ali); // new function with this = ali
aliGreet("Hello"); // Hello, I am Ali
aliGreet("Hi");    // Hi, I am Ali — this is always ali

Fixing lost binding with bind()

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

// Fix the setTimeout binding problem
setTimeout(user.greet.bind(user), 1000); // Hello, I am Ali ✅

Partial application with bind()

bind() can also pre-fill arguments — called partial application:

function multiply(a, b) {
  return a * b;
}

const double = multiply.bind(null, 2); // a is always 2
const triple = multiply.bind(null, 3); // a is always 3

console.log(double(5));  // 10
console.log(triple(5));  // 15

null as the first argument means "I don't care about this" — the function does not use it.


Rule 4 — new Binding

When you call a function with new, a brand new object is created and this refers to it.

function Person(name, age) {
  this.name = name; // this = new object
  this.age = age;
}

const ali = new Person("Ali", 22);
console.log(ali.name); // Ali
console.log(ali.age);  // 22

When new is used:

  1. A new empty object is created
  2. this is set to that new object
  3. The function runs
  4. The new object is returned automatically

Arrow Functions and this

Arrow functions do not have their own this. They inherit this from the surrounding scope where they were defined — called lexical this.

const user = {
  name: "Ali",
  friends: ["Sara", "Zara", "Omar"],

  showFriends() {
    // 'this' here is user — regular method
    this.friends.forEach(friend => {
      // Arrow function inherits 'this' from showFriends
      console.log(`${this.name} is friends with ${friend}`);
    });
  }
};

user.showFriends();
// Ali is friends with Sara
// Ali is friends with Zara
// Ali is friends with Omar

Without an arrow function:

showFriends() {
  this.friends.forEach(function(friend) {
    // Regular function — 'this' is lost here
    console.log(`${this.name} is friends with ${friend}`);
    // this.name is undefined
  });
}

Arrow functions fix the this problem inside callbacks naturally — no need for bind() or saving this to a variable.

The old workaround — self or that

Before arrow functions, developers used a workaround:

showFriends() {
  const self = this; // save reference to this
  this.friends.forEach(function(friend) {
    console.log(`${self.name} is friends with ${friend}`); // use saved reference
  });
}

Arrow functions make this unnecessary.

Arrow functions and object methods

Arrow functions inherit this from where they are defined — not from the object that calls them. This makes them wrong for object methods.

const user = {
  name: "Ali",

  // ❌ Arrow function — this is not user
  greet: () => {
    console.log(`Hello, I am ${this.name}`); // undefined
  },

  // ✅ Regular function — this is user
  greet() {
    console.log(`Hello, I am ${this.name}`); // Ali
  }
};

this in Classes

Inside a class method, this refers to the instance — as long as the method is called on the instance.

class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    // ❌ Regular function loses this
    setInterval(function() {
      this.seconds++; // this is undefined or global
    }, 1000);
  }

  startFixed() {
    // ✅ Arrow function inherits this from startFixed
    setInterval(() => {
      this.seconds++; // this is the Timer instance
      console.log(this.seconds);
    }, 1000);
  }
}

const timer = new Timer();
timer.startFixed(); // 1, 2, 3...

Class field arrow functions — permanently bound methods

class Button {
  constructor(label) {
    this.label = label;
    this.clickCount = 0;
  }

  // Arrow function as class field — this is permanently bound
  handleClick = () => {
    this.clickCount++;
    console.log(`${this.label} clicked ${this.clickCount} times`);
  };
}

const btn = new Button("Like");

// Safe to pass as callback — this is always the instance
document.querySelector("button").addEventListener("click", btn.handleClick);

handleClick is defined as an arrow function class field — this is permanently bound to the instance. You can safely pass it as a callback without bind().


this in Event Handlers

In a regular function event handler, this is the element that fired the event.

const btn = document.querySelector("button");

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

In an arrow function event handler, this is inherited from the outer scope — not the element.

btn.addEventListener("click", () => {
  console.log(this); // outer scope this — probably window
  // ❌ cannot use this to reference the button
});

Use regular functions for event handlers when you need this to be the element. Use arrow functions when you need this to be the surrounding class or object.


Determining this — A Practical Checklist

When you see a function call and wonder what this is — ask these questions in order:

1. Is it called with new?
   → this = new empty object

2. Is it called with call(), apply(), or bind()?
   → this = explicitly provided object

3. Is it called as object.method()?
   → this = object to the left of the dot

4. Is it an arrow function?
   → this = inherited from the surrounding scope

5. None of the above?
   → this = global object (or undefined in strict mode)

A Real Example — Event Handler in a Class

class SearchBar {
  constructor(inputId, resultsId) {
    this.input = document.getElementById(inputId);
    this.results = document.getElementById(resultsId);
    this.query = "";

    // Arrow function — this is always the SearchBar instance
    this.input.addEventListener("input", (e) => {
      this.query = e.target.value;
      this.search();
    });

    this.input.addEventListener("keydown", (e) => {
      if (e.key === "Escape") this.clear();
    });
  }

  async search() {
    if (!this.query.trim()) {
      this.results.innerHTML = "";
      return;
    }

    try {
      const response = await fetch(`/api/search?q=${this.query}`);
      const data = await response.json();
      this.renderResults(data);
    } catch (error) {
      console.error("Search failed:", error);
    }
  }

  renderResults(items) {
    this.results.innerHTML = items
      .map(item => `<li>${item.title}</li>`)
      .join("");
  }

  clear() {
    this.query = "";
    this.input.value = "";
    this.results.innerHTML = "";
  }
}

const search = new SearchBar("search-input", "search-results");

All event handlers use arrow functions — this always refers to the SearchBar instance. Methods call each other through this. No bind() calls needed anywhere.


Summary

  • this is determined at call time — not at definition time
  • Default binding — standalone function call — this is global or undefined in strict mode
  • Implicit bindingobject.method()this is the object to the left of the dot
  • Implicit binding can be lost when a method is extracted and called separately
  • Explicit bindingcall(), apply(), bind() — sets this explicitly
  • call(obj, arg1, arg2) — calls immediately with this set to obj
  • apply(obj, [args]) — same as call but arguments as array
  • bind(obj) — returns a new function with this permanently set — does not call immediately
  • new bindingnew Fn()this is the newly created object
  • Arrow functions do not have their own this — they inherit it from the surrounding scope
  • Use arrow functions for callbacks inside methods — use regular functions for methods themselves
  • Class field arrow functions — handleClick = () => {} — permanently bind this to the instance

On this page