DEV Community

Cover image for How not to be afraid of the 'this' keyword
Amit Barletz
Amit Barletz

Posted on

How not to be afraid of the 'this' keyword

To start talking about the 'this' keyword, we should first understand where it comes from.
When a function (or method) gets invoked, it has 2 phases: the creation phase, and the execution phase.

There are a lot of things to discuss when it comes to those phases, and a lot of concepts like execution context, lexical environment, variable environment, scope and scope chain (But don't worry, I will discuss them in depths in the next article). Therefore, for simplicity, in this article, we only need to know that the value of 'this' keyword is NOT static.

It depends on how the function is called, and its value is only assigned when the function is actually called.

What do I mean by "It depends on how the function is called"? glad you asked!
In JavaScript, there are different ways in which functions can be called and as a result, the 'this' keyword gets different value:

1. Simple function call:

In this case, the 'this' keyword points to the global object - the window,
but in 'strict mode' the 'this' keyword will be undefined.

'use strict'
console.log(this); // window

// es5 way for writing function
const calcAgeES5 = function(birthYear) {
  console.log(new Date().getFullYear() - birthYear);
  console.log(this); // undefined (without the 'use strict' - window)
}

calcAgeES5(1991);

// es6 way for writing function
const calcAgeES6 = birthYear => {
  console.log(new Date().getFullYear() - birthYear);
  console.log(this); // window
}

calcAgeES6(1991);
Enter fullscreen mode Exit fullscreen mode

2. Method:

A method is a function attached to an object. In this case the 'this' keyword points to the object on which the method is called, or in other words, it points to the object that is calling the method.

const marry = {
  birthYear: 1988,
  calcAge: function() {
    console.log(this) // marry
    return new Date().getFullYear() - this.birthYear;
  }
}
marry.calcAge();


const joe = {
  birthYear: 2010
}

joe.calcAge = marry.calcAge;
joe.calcAge(); // this => joe
Enter fullscreen mode Exit fullscreen mode

In the following example we save the 'calcAge' method called on 'marry' to a variable called 'func'. When we will log 'func' we will see the method printed to the console:
ฦ’ () {
return new Date().getFullYear() - this.birthYear;
console.log(this);
}

But, on a strict mode, when we will call func(), the 'this' keyword of that execution context will be undefined because it's a regular function call
that is not attached to any object. Without the 'use strict' the 'this' keyword will be the window object.

'use strict'
const func = marry.calcAge;
console.log(func) // log the function
func(); // this => undefined
Enter fullscreen mode Exit fullscreen mode

3. Arrow functions:

Arrow functions do not get their own 'this' keyword, they get the 'this' keyword of the surrounded function (the parent function).
It's called the lexical 'this' keyword because it simply gets picked up from the outer lexical scope.
In the following example, the parent scope is the global scope because the 'marry' object lives in the global scope, therefore the 'this' keyword is the window.

const marry = {
  firstName: 'Marry',
  birthYear: 1988,
  calcAge: function() {
    console.log(this) // marry
    return new Date().getFullYear() - this.birthYear;
  },
  greet: () => {
    console.log(this); // window
    console.log(`Hello ${this.firstName}`);
  }
}

marry.greet(); // Hey undefined
Enter fullscreen mode Exit fullscreen mode

NOTE: variables declared with 'var' create properties on the global object, therefore in this case where we declared firstName with 'var' and we will call 'marry.greet()', we will get 'Hello Tomas'. It happens because when the greet method gets called it searches for 'firstName' variable in its local scope, does not find it, and go up in the scope chain until it finds it on the window object, there we have window.firstName because of the declaration with 'var'.

var firstName = 'Tomas';
marry.greet(); // Hello Tomas
Enter fullscreen mode Exit fullscreen mode

The way to fix the problem with the 'greet' method is to write it in a form of a regular function and not an arrow function.

const marry = {
  firstName: 'Marry',
  birthYear: 1988,
  calcAge: function() {
    console.log(this) // marry - the object that call the method
    return new Date().getFullYear() - this.birthYear;
  },
  greet: function() {
    console.log(this); // marry
    console.log(`Hello ${this.firstName}`); // Hello Marry
  }
}

marry.greet();
Enter fullscreen mode Exit fullscreen mode
Function inside a method:
const marry = {
  birthYear: 1988,
  calcAge: function() {

    const isMillenial = function() {
      console.log(this.birthYear >= 1981 && this.birthYear <= 1996); // undefined
    }
    isMillenial();
    return new Date().getFullYear() - this.birthYear;
  }
}
marry.calcAge();
Enter fullscreen mode Exit fullscreen mode

'isMillenial' is a regular function call even though it happens inside of a method, and as we have learned earlier in this article inside a regular function call the 'this' keyword is the global object - window (and undefined in 'use strict' mode). There are 2 solutions for the "problem":

i. Outside the 'isMillenial' function save the 'this' to a variable:

const self = this; // self or that
const isMillenial = function() {
  console.log(self.birthYear >= 1981 && self.birthYear <= 1996); // true
}
isMillenial();
Enter fullscreen mode Exit fullscreen mode

ii. Use arrow function which takes the 'this' of his surrounded environment, which in this case is 'calcAge' method, which its this' keyword is 'marry' object

const marry = {
  birthYear: 1988,
  calcAge: function() {

    const isMillenial = () => {
      console.log(this.birthYear >= 1981 && this.birthYear <= 1996); // true
    }
    isMillenial();
    return 2020 - this.year;
  }
}
marry.calcAge();
Enter fullscreen mode Exit fullscreen mode

4. The 'new' operator

To explain the new operator we first need to understand what is a constructor function.
A constructor function is a function that used as a blueprint to create objects, therefore when we call the function, it must be with the new operator
and when we write a constructor function, the name should start with a capital letter.

Constructor functions are used to stimulate classes which we have now in ES6 but as syntactic sugar.
An arrow function cannot be a function constructor because as I have stated, it does not have its own 'this' keyword.
Only function declaration and function expression can be a constructor function.

const Person = function(firstName, birthYear) {
  console.log(this); // Person {}
  this.firstName = firstName;
  this.birthYear = birthYear;

  // NEVER DO THIS
  this.calcAge = function() {
    console.log(2020 - this.birthYear);
  }
}

const amit = new Person('Amit', 1991);
console.log(amit); // Person {firstName: "Amit", birthYear: 1991}
Enter fullscreen mode Exit fullscreen mode

When we call a constructor function with the new operator there are 4 steps that happen behind the scenes

  1. new empty object is created
  2. the function is called and 'this' keyword point on the newly created object.
  3. the newly created object has a link to the prototype (on our example: Person).
  4. the new object created on step 1 returned from the constructor function.

NOTE: you should never create a method inside a constructor function because if that function has many methods, each object that builds based on it, would carry around all the methods. Instead, we should use prototype inheritance, but this is a topic for another article.

5. call, apply, bind

Help us to set the 'this' keyword manually

const lufthansa = {
  airline: 'Lufthansa',
  iataCode: 'LH',
  bookings: [],
  book(flightNum, name) {
    console.log(`${name} booked a seat on ${this.airline} flight ${this.iataCode}${flightNum}`);
    this.bookings.push({
      flight: `${this.iataCode}${flightNum}`,
      passengerName: name
      })
  }
}
lufthansa.book(239, 'John Lennon');
lufthansa.book(447, 'Amy Winehouse');
Enter fullscreen mode Exit fullscreen mode

Now, let's say we have another airline, with different properties but it still needs the book method.
We can copy and paste that method, but it's bad practice. What we should do is to store the method in a variable, so we can
use it in other places. The reason why we can do it like that is that in js functions are first-class citizens.

const book = lufthansa.book();
book(123, 'Marge Simpson'); // Cannot read property airline of undefined
Enter fullscreen mode Exit fullscreen mode

Because 'book' is a regular function call, the 'this' keyword points to undefined (in strict mode).

The way to fix it is to tell JS explicitly what the 'this' keyword should be and here come call, apply, and bind.

  • call && apply: functions that their first argument is the one we want the 'this' keyword to point on. The other arguments are the argument which the function we call on the call or apply methods takes. The difference between call and apply is that apply gets the argument as an array (or an array-like object) and 'call' gets them individually.
const elal = {
  airline: 'Elal',
  iataCode: 'EL',
  bookings: []
}

book.call(elal, 123, 'Marge Simpson'); // 'Marje Simpson' books a seat on Elal flight EL123
book.apply(elal, [789, 'Alice Cooper']); // 'Alice Cooper' books a seat on Elal flight EL789
Enter fullscreen mode Exit fullscreen mode
  • bind: also allows us to manually set the 'this' keyword for any function call. The difference is that bind does not immediately call the function, instead it returns a new function where the 'this' keyword set to the provided value.
const bookEl = book.bind(elal);
bookEl(123, 'Marge Simpson') // 'Marje Simpson' books a seat on Elal flight EL123

// OR we can provide arguments (partial application)
const bookEl123 = book.bind(elal, 123);
bookEl123('Marge Simpson') // 'Marje Simpson' books a seat on Elal flight EL123
bookEl123('Diana Ross') // 'Dianna Rose' books a seat on Elal flight EL123
Enter fullscreen mode Exit fullscreen mode

There are cases when we do not mind what the 'this' keyword is, but we still use bind, for example in a partial application when we preset parameters.
Be aware that the argument you want to preset must be the first argument;

const addTax = (rate, value) => value + value * rate;

const addTax30 = addTax(null, 0.3);
addTax30(200);
Enter fullscreen mode Exit fullscreen mode

6. Event listener:

In an event handler function, the 'this' keyword always points to the DOM element that the handler function is attached to.

<button class="buy">Buy a new plane</button>
Enter fullscreen mode Exit fullscreen mode
const lufthansa = {
  airline: 'Lufthansa',
  iataCode: 'LH',
  bookings: []
}

lufthansa.planes = 300;
lufthansa.byPlane = function() {
  console.log(this); // <button class="buy">Buy a new plane</button>
  this.planes++;
  console.log(this.planes); // NaN
}

document.querySelector('.buy').addEventListener('click', lufthansa.byPlane);
Enter fullscreen mode Exit fullscreen mode

If we will run this code, the line we log 'this' to the console will give us the reference to the DOM element the handler function is attached to,
therefore at the line, we log Lufthansa's planes to the console we will get NaN.

The way to fix it is to use the bind method because in event listener we don't want to immediately call the function, we just pass a reference to the function
which will be called when the event accrues.

document.querySelector('.buy').addEventListener('click', lufthansa.byPlane.bind(lufthansa));
Enter fullscreen mode Exit fullscreen mode

Conclusions:

The 'this' keyword isn't static. It depends on how the function is called, and its value is only assigned when the function is called.

In this article we have covered many cases when the 'this' keyword gets different values, so to summarize the article I am going to tell you what the 'this' keyword will never be:

  • 'this' will never point to the function in which we are using it.
  • 'this' will never point to the variable environment of the function.

As a side note, I would like to share with you one of the reasons I decided to write the first blog post about the 'this' keyword.
When I started learning JavaScript and going to my first interviews I was asked a lot about the 'this' keyword,
and even I went through that topic before every interview when the interviewer asked me a question about the 'this' keyword,
I got confused and nervous and did not get it right.

Thanks for reading, hope you enjoyed and learned something new or at least feel more comfortable with the 'this' keyword now.

๐Ÿ˜‡ Link to the original blog post at my blog:
https://syntactic-sugar.netlify.app/this

Top comments (3)

Collapse
 
equiman profile image
Camilo Martinez

Excellent post ๐Ÿค˜๐Ÿ‘.

Just one thing. Is not recommended to use self = this because on window exists a property called self. It can be lead to errors or mistakes.

developer.mozilla.org/en-US/docs/W...

Collapse
 
abrl91 profile image
Amit Barletz

Thank you for your comment. I know itโ€™s not recommended, but it is still a way to deal with the โ€˜thisโ€™ keyboard, especially in old code bases. Therefore I decided to included it in this article.

Collapse
 
jackent2b profile image
Jayant Khandelwal

Nicely Framed, amit!