DEV Community

Nikolas ⚡️
Nikolas ⚡️

Posted on

Is this (keyword) a problem?

One of the trickiest part of Javascript language is this mechanism. It is used to refer to the object that is currently executing the code.
It is not surprising that questions about this are the part of most coding interviews. Let's dive into it step by step. At the end
I'll show you great set of questions that will help you to evaluate the value of this in any context.

this in global Context

At first, let's look at the global context.

// in browser
this === global // true
Enter fullscreen mode Exit fullscreen mode

Top-level this always points to the global object. It is worth to mention that this behavior is the same both for strict mode and non-strict mode.

// in node
this === global // true - only within the node REPL

// when excecuting the same code within a node module
this === module.exports // true
Enter fullscreen mode Exit fullscreen mode

this in function calls

Usually the value of a function's this is determined by the context in which the function is called. That means the this
value may be different each time the function is called.

function simpleCall() {
    console.log(this); // global object
}
Enter fullscreen mode Exit fullscreen mode

If we are in strict mode, the value of this is undefined in the global context.

"use strict";

function simpleCall() {
    console.log(this); // undefined
}
Enter fullscreen mode Exit fullscreen mode

With that knowledge let's assign a property to the global object and check the value of this in the function call one more time:

function simpleCall() {
    this.name = "John";
    return this.name;
}

simpleCall(); // John
Enter fullscreen mode Exit fullscreen mode

This could be a problem in some cases. The following examples shows how we can assign properties to the global object without any control.
The way to avoid this is to enable a strict mode. That prevents us from accidentally creating global variables, which is a good thing:

function simpleCall() {
    this.name = "John"; // Error: cannot set property 'name' of undefined
    return this.name;
}

simpleCall(); // Error: cannot set property 'name' of undefined
Enter fullscreen mode Exit fullscreen mode

this in constructor calls

Here's a simple example of Coffee function that accepts and initializes two properties:

function Coffee(name, price) {
    this.name = name;  
    this.price = price;
}

const coffee = new Coffee("Cappuccino", 2.5);
console.log(coffee) // undefined
Enter fullscreen mode Exit fullscreen mode

You should probably see the problem with the code above. this within the function refers to global object because we are not in a strict mode.
Now we have accidentally created two global variables: name and price. Enabling a strict mode also won't solve this issue.
Since the this is set to undefined we cannot assign properties to it - we will get an error - at least we didn't pollute the global object.

There is a little hint about the problem. The function name is uppercased, so we were supposed to use new keyword to create an object:

function Coffee(name, price) {
    console.log(this) // Coffee {}
    this.name = name;  
    this.price = price;
}

const coffee = new Coffee("Cappuccino", 2.5);

console.log(coffee) // Coffee { name: 'Cappuccino', price: 2.5 }
console.log(global.name) // undefined
console.log(global.price) // undefined
Enter fullscreen mode Exit fullscreen mode

In JavaScript, a function call with a new operator in front of it turns it into a constructor call.
When a function is invoked as a constructor, a brand-new object is created for us automatically, and that new object
is then used as the this binding for the function call.

this in method calls

It’s common that an object method needs to access the information stored in the object to do its job. To access the object,
a method can use this keyword.

To evaluate the value of this in a method call, we need to know the context of the call.

The value of this is the object "before dot", the one used to called the method.

const coffee = {
    name: "Cappuccino",
    price: 2.5,
    orderCoffee() {
        console.log(`Can I get ${this.name}, please?`); // Can I get Cappuccino, please? (this === coffee)
    }
}
Enter fullscreen mode Exit fullscreen mode

To better illustrate this case let's create the function ad assign it to two different objects:

const coffee = {price: 2.5,}
const tea = {price: 1.5} 

function getPrice() {
    console.log(this.price)
}

coffee.price = getPrice;
tea.price = getPrice;

// "this" inside the function is the object that called the method
coffee.price(); // 2.5 (this == coffee)
tea.price(); // 1.5 (this == tea)
Enter fullscreen mode Exit fullscreen mode

It is a great moment demonstrate another case with a complex property chain like this below:

city.district.cafe.coffee.price(); // 2.5
Enter fullscreen mode Exit fullscreen mode

We're still getting the same result. The receiver of the method call is still the most immediate property before the method - which is coffee. The
city.district.cafe prefix doesn't influence this binding.

Real difficulty with understanding this behavior in Javascript starts when a method loses its receiver. Going back to our example code:

const coffee = {
    name: "Cappuccino",
    price: 2.5,
    orderCoffee() {
        console.log(`Can I get ${this.name}, please?`); // Can I get Cappuccino, please? (this === coffee)
    }
}

const yourOrder = coffee.orderCoffee;
yourOrder(); // Can I get undefined, please? (this === global)
Enter fullscreen mode Exit fullscreen mode

I'm sure that you don't want to order undefined 😅. This outcome is caused by the fact that the method loses its receiver. The
function is called in global scope and we don't have the object "before dot" anymore.

We can came across a similar problem when using setTimeout function. setTimeout function is a method of the global object and
it will call our function with this set to the global object. The first possible solution for this is wrapping the function
in a function that will set this to the object that called the method. After this change coffee.orderCoffee will be invoked
as a method.

const coffee = {
    name: "Cappuccino",
    price: 2.5,
    orderCoffee() {
        console.log(`Can I get ${this.name}, please?`);
    }
}

setTimeout(function() {
    coffee.orderCoffee(); // Can I get Cappuccino, please? (this === coffee)
}, 1000);
Enter fullscreen mode Exit fullscreen mode

Using .bind(), .call() and .apply(),

Functions provide a built-in method bind that allows to create a new function with a preset context. Here's the basic syntax:

const bounded = func.bind(context);
Enter fullscreen mode Exit fullscreen mode

bounded is a special function-like object, that is callable as function and passes the call to func setting this = context.
In other words bounded is a function that will be called with this set to context.

Now let’s try with an object method:

const coffee = {
    name: "Cappuccino",
    price: 2.5,
    orderCoffee() {
        console.log(`Can I get ${this.name}, please?`);
    }
}

setTimeout(coffee.orderCoffee.bind(coffee), 1000); // Can I get Cappuccino, please? (this === coffee)

const order = coffee.orderCoffee.bind(coffee);
// can be run withoud an object
order() // Can I get Cappuccino, please? (this === coffee)
Enter fullscreen mode Exit fullscreen mode

bind will create a new orderCoffee function that will be called with this permanently set to coffee.

Now you can ask - what about .call() and .apply()?

The call() method calls a function with a given this value and arguments provided individually. The main differces between call and bind is that call
method:

  • excecutes the function it was called on right away
  • accepts additional parameters
  • the call method does not make a copy of the function but uses the original one.

call and apply serve the same purpose. The only difference is that apply accepts an array of arguments, whereas
call accepts a comma-separated list of arguments.

function getOrder(coffee, size) {
    console.log(`Customer: ${this.name} ordered: ${size} ${coffee}`);
}

const coffee = {
    name: "Giovanni Giorgio",
}

getOrder.call(coffee, "cappuccino", "large"); // Customer: Giovanni Giorgio ordered: large cappuccino
getOrder.apply(coffee, ["cappuccino", "large"]); // Customer: Giovanni Giorgio ordered: large cappuccino
Enter fullscreen mode Exit fullscreen mode

Arrow functions

Imagine that you have to forget about everything that you've learned so far and start from scratch. It is not fully true,
but behavior of this in arrow functions is slightly different comparing to function declarations or function expressions.

An arrow function does not have its own this binding. It is bound to the this of the enclosing execution context.

const coffee = {
    name: "Cappuccino",
    price: 2.5,
    orderCoffee: () => {
        console.log(`Can I get ${this.name}, please?`);
    }
}

coffee.orderCoffee(); // Can I get undefined, please? (this === global)
Enter fullscreen mode Exit fullscreen mode

Assigning the property name to the global object would solve this issue.

global.name = "Espresso"

const coffee = {
    name: "Cappuccino",
    price: 2.5,
    orderCoffee: () => {
        console.log(`Can I get ${this.name}, please?`);
    }
}

coffee.orderCoffee(); // Can I get Espresso, please? (this === global)
Enter fullscreen mode Exit fullscreen mode

As you can see to understand the value of this in arrow functions we have to look to the first non-arrow function
scope. That is why arrow function from the example above will not display "Cappuccino" - it's because coffee is an object
and not a function scope.

This knowledge could be extremely useful when you want to access this within a callback. Let's leave the coffee related
examples for a while and take a look at the code below that uses setInterval:

const counter = {
    count: 0,
    incrementCouter() {
        setInterval(function() {
            console.log(++this.count)
        }, 1000)
    }
}

counter.incrementCouter(); // NaN, NaN, NaN and so on...
Enter fullscreen mode Exit fullscreen mode

It does not work. Why? this binding within our function expression refers to the global object because that's how setInterval works.
Since there is no count property in the global object, it will not be able to access it. We're getting NaN as a result
because incrementing ++undefined returns NaN.

Here is the solution:

const counter = {
    count: 0,
    incrementCouter() {
        setInterval(() => {
            console.log(++this.count)
        }, 1000)
    }
}

counter.incrementCouter(); // 1, 2, 3...
Enter fullscreen mode Exit fullscreen mode

Important note. this binding in arrow functions cannot be set explicitly. this argument passed to an arrow function
using call(), apply() or bind() is ignored. In other words, it doesn't matter how we call an arrow function.
this argument will always refer to the this value that was captured when the arrow function was created.

Summary

For the sake of simplicity, there is a list of questions that you should answer while evaluating the value of this.
The order of the questions is really important.

  • is the function called with new?
    Then this refers to a constructor object.

  • is the function called with call, apply or bind?
    Then this refers to the context passed as the argument call, apply or bind methods

  • is the function called as a method, ie: obj.method()?
    Then this refers to the object "before the dot" in the method call

  • is the function called in the global scope?
    Then this refers to the global object, which is window in browsers.

Top comments (0)