DEV Community

Alexander Dementev
Alexander Dementev

Posted on

Navigating the Complexities of ‘this’ in JavaScript

this in JavaScript was the most confusing thing in my web development career since I touched JS for the first time. Even now, I use to re-validate my knowledge quite often.

In this article, we will explore the different ways in which this can be used in JavaScript and how we can control its value to create more maintainable and readable code.

I hope this article will be helpful for beginners in the JS world who might get scared of this little monster and for experienced developers to refresh their knowledge as I do.

Where We Use THIS in JavaScript

We often use the this keyword in JavaScript when working with object-oriented programming and event listeners. In general, it refers to an object or any value.

Here and after, I will use objects, but the same applies to classes because they are essentially the same.

For example, let’s say we have a Person object with a name property and a greet method:

// Object usage <--------------
const Person = {
  name: "John",
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

Person.greet();


// Class usage <---------------
class Person {
  name = "John";
  greet() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const person = new Person();
person.greet();
Enter fullscreen mode Exit fullscreen mode

When we call the greet method using Person.greet(), the value of this inside the method will refer to the Person object, and the output will be:

Hello, my name is John
Enter fullscreen mode Exit fullscreen mode

Developers can also use this keyword in JavaScript when working with event listeners. For example, let's say we have a button with an onclick attribute:

<button onclick="console.log(this)">Click Me!</button>
Enter fullscreen mode Exit fullscreen mode

When the button is clicked, this keyword inside the onclick function will refer to the button element that triggered the event. This can be useful when manipulating the button element or its properties.

But there are a lot of nuances…

Let’s take a look at the rules we should stick to working with this:

Rules for THIS working with…

1. Global Context

When this is used in the global context, it refers to the global object. Thanks cap.

In a web browser, the global object is the window object.

console.log(this === window); // true
Enter fullscreen mode Exit fullscreen mode

2. Functions

Note: Henceforth, in this article, I’ll use the word function for all functions created with the function keyword, and arrow function for… guess… you damn right!

When this is used inside a function, its value depends on how the function is called. If the function is called as a method of an object, then this refers to the object that the method belongs to.

const person = {
  name: "John",
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

person.greet(); // Hello, my name is John
Enter fullscreen mode Exit fullscreen mode

If we extract the method from the object and call it as it is, we will get undefined

const person = {
  name: "John",
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
}

const myGreet = greet;
myGreet(); // Hello, my name is undefined
Enter fullscreen mode Exit fullscreen mode

…because, if the function is called without an object context, then this refers to the global object:

function sayHi() {
  console.log(`Hi, my name is ${this.name}`);
}

sayHi(); // Hi, my name is undefined (in a web browser)
Enter fullscreen mode Exit fullscreen mode

Note that if you run this code in the browser console, you might have an empty string in return. This is because modern browsers set window.name to “” by default.

Now let’s take a look at arrow functions:

this in an arrow function will always be the same as this in the surrounding code - where it was created, it inherits context from its ancestor.

const person = {
  name: "John",
  greet: function() {
    // In the function "this" refers to the object "person"
    // because we called it with the dot notation down the code.
    // The arrow function is created in the function with that context
    // So as we call it we get name property
    const arrowGreet = () => {
      console.log(`Hello, my name is ${this.name}`);
    };
    arrowGreet();
  }
}

person.greet(); // Hello, my name is John
Enter fullscreen mode Exit fullscreen mode

Here are interesting things to get started.

What if we create an object method with an arrow function? Let’s take a look:

const person = {
  name: "John",
  greet: () => {
    console.log(`Hello, my name is ${this.name}`);
  }
}

person.greet();
Enter fullscreen mode Exit fullscreen mode

Hello, my name is undefined, because there is no other context other than global. What would be the value of the context property?

const person = {
  context: this
}
Enter fullscreen mode Exit fullscreen mode

Global context — window.

What would be the result of the following code?

const person = {
  name: "John",
  greet: function() {
    const arrowGreet = () => {
      console.log(`Hello, my name is ${this.name}`);
    };

    arrowGreet();
  }
}

const myGreet = person.greet;
myGreet();
Enter fullscreen mode Exit fullscreen mode

Take your time… 😄

Alright.

The answer is Hello, my name is undefined because we called the function without any context, it created the arrow function with a global context that has no name property inside.

We can fix it by slightly changing the code:

const person = {
  name: "John",
  greet: function() {
    const arrowGreet = () => {
      console.log(`Hello, my name is ${this.name}`);
    };

    return arrowGreet; // Here we don't call it immediately but return it
  }
}

// Here we call function with the person context
// so arrowGreet receives it on its creation and is returned. 
const myGreet = person.greet(); // myGreet is our arrow function with saved context
myGreet(); // we call our arrow function that extracts name property from 
       // the saved context - person.
Enter fullscreen mode Exit fullscreen mode

As a result, we get Hello, my name is John.

3. Bind, Call, and Apply Methods

In JavaScript, we can use the bind, call, and apply methods to set the value of this explicitly. These methods provide a way to control the value of this in our functions.

const person = {
  name: "John",
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

const greetPerson = person.greet.bind(person); // Set the value of this to person
greetPerson(); // Hello, my name is John
Enter fullscreen mode Exit fullscreen mode
const person1 = {
  name: "John",
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

const person2 = {
  name: "Jane"
};

person1.greet.call(person2); // Hello, my name is Jane
person1.greet.apply(person2); // Hello, my name is Jane
Enter fullscreen mode Exit fullscreen mode

4. Event Context

When an event occurs in the browser, the value of this keyword is set to the element that triggered the event.

<button onclick="console.log(this)">Click Me!</button>
Enter fullscreen mode Exit fullscreen mode

5. new Keyword

When a function is called with the new keyword, a new object is created, and this keyword refers to that newly created object.

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

const john = new Person("John");
console.log(john.name); // John
Enter fullscreen mode Exit fullscreen mode

6. Strict Mode

In strict mode, the behavior of the this keyword is modified to avoid some of the common pitfalls of JavaScript. When this is used in a global context or in a function (not an arrow function) that is not a method of an object, it is undefined instead of referring to the global object. This helps prevent unintended modifications to the global object and makes the code more predictable.

"use strict";

function sayHi() {
  console.log(this); // undefined
}

sayHi();
Enter fullscreen mode Exit fullscreen mode

In strict mode, when a function is called with the new keyword, this behaves as expected, referring to the newly created object. This behavior is consistent with non-strict mode.

"use strict";

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

const john = new Person("John");
console.log(john.name); // John
Enter fullscreen mode Exit fullscreen mode

When using methods like bind, call, and apply, strict mode does not change the behavior of this. The value of this is explicitly set by these methods, regardless of whether strict mode is enabled.

"use strict";

const person = {
  name: "John",
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

const greetPerson = person.greet.bind(person);
greetPerson(); // Hello, my name is John
Enter fullscreen mode Exit fullscreen mode

In summary, strict mode enforces a more stringent environment for the this keyword, ensuring it is not defaulted to the global object in cases where it might otherwise lead to errors, thus promoting safer and more reliable JavaScript coding practices.

Conclusion

In conclusion, understanding the this keyword in JavaScript is crucial for developing clear and maintainable code, especially when dealing with object-oriented programming and event handling. By mastering the various contexts in which this operates — whether in global, functional, or arrow functions, or when using methods like bind, call, and apply—developers can better predict how their code will behave. Additionally, being aware of nuances such as the effects of strict mode and the new keyword can further enhance code reliability. Familiarity with these concepts allows both beginners and experienced developers to harness the full potential of JavaScript's dynamic capabilities.

END OF LINE ▋

Top comments (0)