Understanding the `this` Keyword in JavaScript: A Clear Guide with Tricky Scenarios

The this keyword in JavaScript is one of the most powerful yet tricky aspects of the language. Its behavior can vary depending on how and where a function is called, which often leads to confusion among developers. In this article, we’ll break down the concept of this, explore its typical use cases, and dive into some tricky scenarios with clear guidance on how to handle them.


What Is this in JavaScript?

In JavaScript, this is a context-sensitive keyword that refers to the object on which a function is executed. Its value is determined at runtime and is highly dependent on the call-site—the location in the code where the function is invoked.

Here’s a quick breakdown:

  • In the global context, this refers to the global object (window in browsers or global in Node.js).

  • Inside an object method, this refers to the object.

  • In a regular function, this can vary depending on whether the function is in strict mode.

  • Arrow functions do not have their own this and inherit it from their surrounding context.


Typical Use Cases of this

1. In the Global Context

In the global scope, this refers to the global object unless in strict mode, where it becomes undefined.

Example:

console.log(this); // Outputs: Window (in browsers)

'use strict';
console.log(this); // Outputs: undefined

2. Inside Object Methods

When a function is called as a method of an object, this refers to the object that owns the method.

Example:

const obj = {
  name: 'JavaScript',
  getName() {
    return this.name;
  }
};

console.log(obj.getName()); // Outputs: JavaScript

3. In Constructor Functions

When a constructor function is used to create an object, this refers to the new instance of the object.

Example:

function Language(name) {
  this.name = name;
}

const js = new Language('JavaScript');
console.log(js.name); // Outputs: JavaScript

4. With Classes

In ES6 classes, this behaves similarly to constructor functions, referring to the instance of the class.

Example:

class Language {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

const js = new Language('JavaScript');
console.log(js.getName()); // Outputs: JavaScript

5. Arrow Functions

Arrow functions do not have their own this and inherit it from the enclosing context. This behavior is particularly useful when working with callbacks.

Example:

const obj = {
  name: 'JavaScript',
  greet() {
    const arrow = () => `Hello, ${this.name}`;
    return arrow();
  }
};

console.log(obj.greet()); // Outputs: Hello, JavaScript

Tricky Scenarios with this

Let’s explore scenarios where this behaves in unexpected ways and how to address them.

1. Standalone Functions

When a function is detached from its object, this no longer refers to the original object—it defaults to the global object (window) or undefined in strict mode.

Example:

const obj = {
  name: 'JavaScript',
  getName() {
    return this.name;
  }
};

const getName = obj.getName;
console.log(getName()); // Outputs: undefined

Solution: Use .bind() to explicitly bind this to the object.

const boundGetName = obj.getName.bind(obj);
console.log(boundGetName()); // Outputs: JavaScript

2. Inside Nested Functions

In a nested function, this does not inherit the value from the outer function. Instead, it defaults to the global object or undefined in strict mode.

Example:

const obj = {
  name: 'JavaScript',
  greet() {
    function innerFunction() {
      console.log(this.name);
    }
    innerFunction();
  }
};

obj.greet(); // Outputs: undefined

Solution 1: Use an arrow function, which inherits this from the surrounding context.

const obj = {
  name: 'JavaScript',
  greet() {
    const innerFunction = () => console.log(this.name);
    innerFunction();
  }
};

obj.greet(); // Outputs: JavaScript

Solution 2: Store this in a variable.

const obj = {
  name: 'JavaScript',
  greet() {
    const self = this;
    function innerFunction() {
      console.log(self.name);
    }
    innerFunction();
  }
};

obj.greet(); // Outputs: JavaScript

3. In Event Handlers

In event handlers, this typically refers to the element that triggered the event. However, using an arrow function in this context changes the behavior.

Example:

document.querySelector('button').addEventListener('click', () => {
  console.log(this); // Outputs: Window
});

Solution: Use a regular function to retain the element as this.

document.querySelector('button').addEventListener('click', function () {
  console.log(this); // Outputs: <button>
});

4. With setTimeout

In a setTimeout callback, this defaults to the global object.

Example:

const obj = {
  name: 'JavaScript',
  greet() {
    setTimeout(function () {
      console.log(this.name);
    }, 1000);
  }
};

obj.greet(); // Outputs: undefined

Solution: Use an arrow function to inherit this.

const obj = {
  name: 'JavaScript',
  greet() {
    setTimeout(() => {
      console.log(this.name);
    }, 1000);
  }
};

obj.greet(); // Outputs: JavaScript

5. Detached Class Methods

When a class method is assigned to a variable, it loses its reference to the class instance.

Example:

class Language {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

const js = new Language('JavaScript');
const getName = js.getName;
console.log(getName()); // Outputs: undefined

Solution: Bind the method to the instance using .bind() or use an arrow function.

class Language {
  constructor(name) {
    this.name = name;
    this.getName = this.getName.bind(this);
  }

  getName() {
    return this.name;
  }
}

const js = new Language('JavaScript');
console.log(js.getName()); // Outputs: JavaScript

Summary

Understanding this is crucial to writing clear and predictable JavaScript code. Here’s a quick guide:

Scenariothis BehaviorSolution
Standalone FunctionDefaults to global object or undefinedUse .bind() to explicitly set this.
Nested FunctionsDefaults to global object or undefinedUse arrow functions or store this.
Event HandlersRefers to event targetUse regular functions for handlers.
setTimeoutDefaults to global objectUse arrow functions.
Detached Class MethodsLoses reference to class instanceUse .bind() or arrow functions.

By mastering these tricky parts and knowing when and how to manage this, you can write robust and maintainable JavaScript code. 🎉

0
Subscribe to my newsletter

Read articles from Eklemis Santo Ndun directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Eklemis Santo Ndun
Eklemis Santo Ndun

Hi there, I'm a developer from Indonesia. I love learning new things especially technology. Now, not only do i love learning new things, but also to write about what i've learnt. In regular work day, i deal a lot with Images files, SQL Server, MS Access and Excel as well. To make my daily target reached with high quality and shorter time, i automate many of my task with help of Python. In some months where my regular task is not much, i spent time develop web apps to automate my colleagues regular task as well. Out of regular work time, i spend time learning and creating project with Javascript (and Html and CSS), learn UI/UX design with Figma, code Python scripts(Machine Learning), and Rust programming language. To help better remembering all things i've learnt, now i learn to write about them as well.