TL;DR:
In JavaScript, the value of this depends entirely on how a function is called, not where it was defined.
It can reference the global object (window/global), the calling object, undefined (in strict mode), or be explicitly bound using .bind(), .call(), or arrow functions.
In this post, we cover:

  • What this points to in different contexts: object methods, global scope, functions, classes, and callbacks
  • How this behaves differently in strict mode
  • The use of .bind() and .call() to control this explicitly
  • Why arrow functions can be lifesavers in callback scenarios
  • How this works with constructor functions and classes By the end, you’ll understand the most misunderstood keyword in JavaScript with clarity.

Introduction

The question that could bring world peace: "What is the value of this?"
If you’ve worked with JavaScript for a while, you’ve probably hit this dreaded error:

Cannot read property 'value' of undefined

If you haven’t seen it yet — don’t worry young padawan, your time will come. 😅

In this post, I’ll break down what this really is in JavaScript. Spoiler: it’s all about who’s calling the function — not where or how it’s written. We’ll explore this in object methods, top-level functions, strict mode, constructors, and even callback hell. Strap in — it’s going to clear up a lot.


In short, in JS, this points to the object that is executing the current function — in other words, the object that defines the execution context of that function.
Said like this, it may sound tricky to grasp, but let’s go through some practical examples.

this in the context of methods

We already know that when a function is part of an object, it’s called a method. In this scenario, it’s easy to deduce what object will be referenced by this, since the function is part of the object itself.

const car = {
  model: "MCL39",
  team: "McLaren",
  showThis() {
    console.log(this)
  }
}

car.showThis(); 
// Object { model: "MCL39", team: "McLaren", showThis: showThis() }

No big mystery here — this points to the object that called the function, which in this case is const car.

In most cases, you can figure this out by using the left-hand rule: the object to the left of the function invocation is usually the context.


this in the context of functions

Okay, but what if my function isn’t being called by an object? What if I’m not even inside a function? What is this then? Well, everything in JS is an object — everything.
So, if in your code, script, or app you’re using a function that isn’t part of an object you created, then it’s part of JS’s global object. This global object can be window in the browser or global in NodeJS.

Here’s an example:

console.log(this); // Window {...}

if (true) {
  console.log(this); // Window {...}
}

function aFunction() {
  console.log(this);
}
aFunction(); // Window {...}

In this case, we see three examples where the context is the same.

  1. The first console.log(this) runs directly in the browser’s console — its execution context is the global window object.

  2. The second one is inside an if() block — again, this points to window. This happens because blocks don’t define context. They define variable scope, but not object context. In the end, it’s just another block belonging to the global object.

  3. The third one is inside the aFunction() function, but it’s being invoked directly in the console, by the browser’s window. So, its context is also window.

To reinforce what we said earlier: as a general rule — with a few exceptions we’ll cover later — the context of this belongs to whoever is calling the function.
We can prove this by checking the window object:

console.log(this);
/*
Window {
  aFunction(): function aFunction()
}
*/

window.aFunction(); // Window {...}

Notice that the window object contains the aFunction() we defined. That’s because we created the function directly in the browser console, which defaults to the window context.


this and strict mode

If you’re using “strict mode”, the return will be undefined. Strict mode is a safeguard that prevents direct access to the global object through this. It’s used by default in React and Node environments, for example.

function aFunction() {
  'use strict';
  console.log(this);
}

const obj = {
  aFunction
}

obj.aFunction(); // Object {...}
aFunction();     // undefined

Here, we explicitly use strict mode inside the function. That way, the global object can’t be referenced through this.
But when we place the same function inside an object and call it through that object, this correctly references the object.
When we invoke the function directly in the console, we get undefined — because strict mode forbids access to the global object (window in this case).

But this can cause problems. What if we need to execute a function using an object we can’t modify?


Binding and this

Imagine I have my accelerate() function and I want it to be executed using my car object — but I don’t have permission to add the function directly to the object.
To solve this, there are some Function API methods that let us manually bind the object to the function’s this.

Let’s start with Function.bind():

const car = {
  speed: "250km/h"
}

function accelerate() {
  return `Accelerating at ${this.speed}`;
}

const boundFunction = accelerate.bind(car);
boundFunction(); // Accelerating at 250km/h

Function.bind() creates a new function whose this context is explicitly set to the object passed as parameter. This new function can then be stored and reused.

Now, what if the function receives arguments? There are two ways: you can pass the argument in the bind() call or call the bound function with arguments later.

const car = {
  speedKm: "250km/h",
  speedMi: "155mph"
}

function accelerate(isMPH) {
  return isMPH 
    ? `Accelerating at ${this.speedMi}`
    : `Accelerating at ${this.speedKm}`;
}

const boundWithArg = accelerate.bind(car, false);
const boundWithoutArg = accelerate.bind(car);

boundWithArg();             // Accelerating at 250km/h
boundWithoutArg(true);      // Accelerating at 155mph
boundWithoutArg(false);     // Accelerating at 250km/h

// If bind was used with a fixed argument
boundWithArg(true);         // Accelerating at 250km/h
boundWithArg(false);        // Accelerating at 250km/h

When you bind with an argument, that argument is fixed and can’t be changed later — so choose the method that fits your case best.

If you don’t need to reuse the function and just want to run it once, use Function.call():

const car = {
  speedKm: "250km/h",
  speedMi: "155mph"
}

function accelerate(isMPH) {
  return isMPH
    ? `Accelerating at ${this.speedMi}`
    : `Accelerating at ${this.speedKm}`;
}

accelerate.call(car, true); // Accelerating at 155mph

There’s not much difference between bind and call in this simple case — but these methods are much more powerful than shown here. This was just to explain the binding concept so we can better understand this.

this and constructors/classes

We already explored constructors and classes in a previous post — and you’ve probably noticed how frequently we use this inside them.

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }
}

Here’s a good opportunity to reinforce why this is so important. A class is instantiated with the new keyword. In that earlier post, I explained that the reason this exists inside a constructor is because the constructor “creates” a this context for the object. The example I used was:

function Pet(name, ownerName) {
  // const this = {};
  this.name = name;
  this.ownerName = ownerName;
  this.identify = () => {
    return `${this.name} belongs to ${this.ownerName}`;
  }
  // return this;
}

But now we can go further: when we use the new keyword, JavaScript automatically binds the this inside the constructor/class to the new object being created.

Back to our code:

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }
}

In this code, notice how the argument name and the class property name are the same. The this.model on the left refers to the instance’s property, while the model on the right is the constructor argument.

Also, when we instantiate an object from this class, this will refer to the new object:

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }

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

const car = new Car("MCL39", "McLaren");
car.showThis(); // Object { model: "MCL39", team: "McLaren" }

this and Callbacks

Let’s simulate the 200ms reaction time a racing driver has before pressing the throttle. We’ll use setTimeout() — which takes two parameters: a delay time and a function to run once that time has passed.

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }

  accelerate() {
    setTimeout(function() {
      console.log(`Accelerating at ${this.model}`);
    }, 200);
  }
}

const car = new Car("MCL39", "McLaren");
car.accelerate(); // Accelerating at undefined

Oops… this.model is undefined? Aren’t we inside the car object?

That’s the catch: we’re inside a callback function, which is just a regular function — and as explained earlier, in normal functions, this refers to the global object (or undefined in strict mode).
Let’s verify:

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }

  showThis() {
    setTimeout(function() {
      console.log(this);
    }, 200);
  }
}

const car = new Car("MCL39", "McLaren");
car.showThis(); // Window {...} or undefined

There are two ways to fix this:

Option 1: Explicit binding with bind()

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }

  accelerate() {
    setTimeout(function() {
      console.log(`Accelerating at ${this.model}`);
    }.bind(this), 200);
  }
}

const car = new Car("MCL39", "McLaren");
car.accelerate(); // Accelerating at MCL39

We’re explicitly binding the this context to the object when passing the function into setTimeout().

Option 2: Using an arrow function

class Car {
  constructor(model, team) {
    this.model = model;
    this.team = team;
  }

  accelerate() {
    setTimeout(() => {
      console.log(`Accelerating at ${this.model}`);
    }, 200);
  }
}

const car = new Car("MCL39", "McLaren");
car.accelerate(); // Accelerating at MCL39

Arrow functions don’t create their own this — they inherit it from the outer context, which in this case is the accelerate() method. That’s why this solution works so well.

For a deeper explanation of why this works and why I recommend arrow functions in this context, check the arrow functions post — it covers the nuance in detail.


Wrapping up

JavaScript’s this can be confusing, but with some practical rules and examples, you can master it:

  • this refers to the calling object in most cases
  • In strict mode, this is undefined unless explicitly bound
  • In global functions, it defaults to the global object (window/global)
  • Arrow functions don’t have their own this, they inherit it from the parent scope
  • .bind(), .call(), and .apply() give you explicit control over this

Knowing how to use this effectively will help you write more predictable, safer code — especially in modern frameworks like React or when designing your own reusable JavaScript modules.

💬 Have you ever been bitten by a confusing this bug?
Let me know in the comments — and let’s help each other write better JavaScript.

📬 And don’t forget: follow me @matheusjulidori for more hands-on JavaScript tips, patterns, and dev-friendly content, every week! Share this content with your peers if you enjoyed it!