Skip to main content

Command Palette

Search for a command to run...

The this Keyword

Stop guessing why your methods are returning undefined and finally master the execution context.

Published
9 min read
The this Keyword

At some point you will write a method on an object, call it directly and it works fine, then pass it somewhere as a callback and it completely falls apart. this.name comes back undefined. The code looks exactly the same to you. The error message is no help at all.

That is this doing something you did not expect, because nobody explained the one idea that makes all of its behavior obvious. This post is built around that idea. Everything else follows from it.

What this Represents

this is the object that called the function. Not where the function was written, not where it lives in the file. Who called it, right now, at this moment.

Think of a customer service line. When someone calls in, the rep picks up and sees the caller's name on screen. That display changes every time a different person rings. The phone line is the function. The name on the display is this.

Here is the simplest version of that in code:

function greet() {
  console.log("Hello, I am " + this.name);
}

const user  = { name: "Rohan", greet };
const admin = { name: "Priya", greet };

user.greet();   // user called it: this = user
admin.greet();  // admin called it: this = admin

// OUTPUT:
// Hello, I am Rohan
// Hello, I am Priya

One function, written once. The dot before the method call is your clue every time: whatever is on the left of that dot when the function runs is this. Keep that in mind as you read the rest of this post.

this In The Global Context

Before any functions or objects, there is already a this. At the very top of a script, outside everything else, it points to the global object. In a browser that is window.

// Browser
console.log(this);            // Window {…}
console.log(this === window); // true

In Node.js the story is slightly different. At the top of a CommonJS file, this is the module's exports object, not Node's global. It looks like an empty object by default because nothing has been exported yet.

// Node.JS
console.log(this);                   // {}
console.log(this === global);         // false
console.log(this === module.exports); // true

You will almost never use the global this on purpose. What matters here is knowing it exists so you are not confused when it shows up unexpectedly in the console or in an error message.

this Inside Objects

When a function is called as a method of an object, this is that object. This is the case that feels the most natural and the one that matches what beginners usually expect. The object called the method, so the object is this.

const player = {
  name:  "Rohan",
  score: 1400,
  showScore() {
    console.log(this.name + " has " + this.score + " points");
  }
};

player.showScore();  // player is on the left: this = player

// OUTPUT:
// Rohan has 1400 points

Swap the caller, swap this. The same function attached to two different objects gives two different results:

const playerOne = { name: "Rohan", score: 1400 };
const playerTwo = { name: "Divya", score: 2100 };

function showScore() {
  console.log(this.name + " has " + this.score + " points");
}

playerOne.show = showScore;
playerTwo.show = showScore;

playerOne.show();  // this = playerOne
playerTwo.show();  // this = playerTwo

// OUTPUT:
// Rohan has 1400 points
// Divya has 2100 points

The Lost this

When you copy a method into a variable and call it, this is gone.

const player = {
  name: "Rohan",
  greet() {
    console.log("Hi, I am " + this.name);
  }
};

player.greet();          // `this` doesn't get lost

const fn = player.greet;  // just the function, no object
fn();                     // `this` gets lost

// OUTPUT:
// Hi, I am Rohan
// Hi, I am undefined

Passing a method as a callback is the same problem. setTimeout(player.greet, 1000) hands over the function with no object. When it runs a second later, this is not player. This shows up constantly in real code.

this Inside Regular Functions

When a plain function is called with nothing on the left of the dot, this defaults to the global object. In strict mode it becomes undefined instead, which is actually safer because it fails loudly.

// Non-strict. this defaults to Window
function whoAmI() {
  console.log(this);
}

whoAmI(); // Window {…} in the browser

// Strict mode. this becomes undefined
"use strict";

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

whoAmIStrict(); // undefined

ES modules are strict by default. If your files use import and export, strict mode is already on. Most modern JavaScript projects run in modules, so you already have this without thinking about it.

Nested Functions Lose this

A regular function written inside a method does not inherit the method's this. It resets. The inner function is called as a plain function, so it gets the global default. Proximity in code has no effect; only the call site matters.

const game = {
  title: "Space Run",
  start() {
    console.log(this.title); // "Space Run". this = game

    function inner() {
      console.log(this.title); // undefined. this reset to Window
    }

    inner(); // called alone. no object on the left
  }
};

game.start();

// OUTPUT:
// Space Run
// undefined

The fix for this is arrow functions, which do not reset this.

How the Calling Context Changes this

Because this is set at call time, you can control it. JavaScript gives you three methods for this: call, apply, and bind.

call() and apply()

Both invoke the function immediately and let you specify the object to use as this. The difference is just how you pass other arguments. call takes them one by one. apply takes an array.

function introduce(city, sport) {
  console.log(this.name + " is from " + city + " and plays " + sport);
}

const maya = { name: "Maya" };
const leo  = { name: "Leo"  };

introduce.call(maya,  "Mumbai",  "cricket");     // args one by one
introduce.call(leo,   "Lisbon",  "football");
introduce.apply(maya, ["Mumbai", "cricket"]);   // args as array

// OUTPUT
// Maya is from Mumbai and plays cricket
// Leo is from Lisbon and plays football
// Maya is from Mumbai and plays cricket

bind()

bind does not call the function. It returns a new copy with this permanently locked. That copy can be stored, passed around, and called later. It will always have the same this. This is the direct fix for the lost-this trap.

const player = {
  name: "Rohan",
  greet() {
    console.log("Hi, I am " + this.name);
  }
};

// Without bind, this is lost when passed as a callback
setTimeout(player.greet, 0);

// With bind, this is locked to player permanently
setTimeout(player.greet.bind(player), 0);

// OUTPUT:
// Hi, I am undefined
// Hi, I am Rohan
Method Calls immediately Args format Returns
call(obj, a, b) Yes one by one result of the function
apply(obj, [a, b]) Yes as an array result of the function
bind(obj, a, b) No one by one a new bound function

Arrow Functions and this

Arrow functions do not have their own this. They look at the code around them at the time they were written and borrow whatever this was in that scope. That value never changes, regardless of how the arrow function gets called later.

This is exactly what fixes the nested function problem:

const game = {
  title: "Space Run",
  start() {
    // Regular function: this resets to Window inside here
    function inner() {
      console.log("regular: " + this.title);
    }

    // Arrow: borrows this from start(), which is game
    const innerArrow = () => {
      console.log("arrow: " + this.title);
    };

    inner();       
    innerArrow();  
  }
};

game.start();

// OUTPUT:
// regular: undefined
// arrow: Space Run

Where Arrow Functions Go Wrong

Arrow functions work great inside methods. Used as a method directly on an object, they break. The surrounding scope at write time is the global scope, so that is what gets borrowed.

// Arrow as method: breaks
const player = {
  name: "Rohan",
  greetArrow: () => {
    // borrows global scope
    console.log(this.name);
  }
};

player.greetArrow();
// undefined

// Regular as method: works
const player = {
  name: "Rohan",
  greetRegular() {
    // set at call time = player
    console.log(this.name);
  }
};

player.greetRegular();
// Rohan

The practical rule: write methods on objects and classes with regular function syntax. Write callbacks inside those methods with arrow functions. That combination gives you the right this in both places without needing to think too hard about it.

All the Contexts at Once

Every case in this post comes from the same question: who is calling the function right now? Here is how each calling pattern maps to a result, and how to recognise which one you are looking at:

When you are unsure which case applies, ask the same question every time: what is on the left of the dot when this function is called? If there is an object, that is this. If there is nothing, you are in the plain function case. If you used call, apply, or bind, you set it yourself. Arrow functions are the only exception, they ignore the call site entirely and use whatever was in scope when they were written.

Most of the bugs you will encounter are just the extracted-method case. A function that worked on an object, passed somewhere as a callback, lost its caller. bind fixes it. Arrow callbacks inside methods fix it too. Once you see what is happening, the fix is never complicated.

REFERENCES:

Simply JavaScript

Part 11 of 25

JavaScript is a quirky language. To master it, one should know to avoid its hidden traps along with its logic. This series showcase my journey through JS: the pain points, the breakthroughs, and the coding standards that I adopted from my mentors.

Up next

Spread & Rest

A practical guide to cloning objects, merging arrays, and handling infinite function arguments.