JavaScript
Article
By Ivaylo Gerchev

The Final Steps to Mastering JavaScript’s “this” Keyword

By Ivaylo Gerchev
Help us help you! You'll get a... FREE 6-Month Subscription to SitePoint Premium Plus you'll go in the draw to WIN a new Macbook SitePoint 2017 Survey Yes, let's Do this It only takes 5 min

In a previous article we learned the fundamentals of using JavaScript’s this keyword properly. We saw that the crucial factor in determining what this refers to, is to find out the current execution context. However, this task can be a bit tricky in situations where the context gets changed in a way we don’t expect. In this article I will highlight when this might happen and what we can do to remedy it.

Fixing Common Issues

In this section we’ll explore some of the most common issues arising from the use of the this keyword and we’ll learn how to fix them.

1. Using this in Extracted Methods

One of the most common mistakes that people make is when trying to assign an object’s method to a variable and expecting that this will still point to the original object. As we can see from the following example, that simply doesn’t work.

var car = {
  brand: "Nissan",
  getBrand: function(){
    console.log(this.brand);
  }
};

var getCarBrand = car.getBrand;

getCarBrand();   // output: undefined

JS Bin

Even though getCarBrand appears to be a reference to car.getBrand(), in fact, it’s just another reference to getBrand() itself. We already know that the call-site is what matters in determining the context, and here, the call-site is getCarBrand(), which is a plain and simple function call.

To prove that getCarBrand points to a baseless function (one which isn’t bound to any specific object), just add alert(getCarBrand); to the bottom of the code and you’ll see the following output:

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

getCarBrand holds just a plain function, which is no longer a method of the car object. So, in this case, this.brand actually translates to window.brand, which is, of course, undefined.

If we extract a method from an object, it becomes a plain function again. Its connection to the object is severed, and it no longer works as intended. In other words, an extracted function is not bound to the object it was taken from.

So how can we remedy this? Well, if we want to keep the reference to the original object, we need to explicitly bind the getBrand() function to the car object when we assign it to the getCarBrand variable. We can do this by using the bind() method.

var getCarBrand = car.getBrand.bind(car);
getCarBrand();   // output: Nissan

Now, we get the proper output, because we successfully redefine the context to what we want it to be.

2 this Used in Callbacks

The next issue occurs when we pass a method (that uses this as a parameter) to be used as a callback function. For example:

<button id="btn" type="button">Get the car's brand</button>

var car = {
  brand: "Nissan",
  getBrand: function(){
    console.log(this.brand);
  }
};

var el = document.getElementById("btn");
el.addEventListener("click", car.getBrand);

JS Bin

Even though we use car.getBrand, we actually only get the function getBrand() which is attached to the button object.

Passing a parameter to a function is an implicit assignment, so what happens here is almost the same as in the previous example. The difference is that now car.getBrand is not explicitly assigned, but implicitly. And the result is pretty much the same—what we get is a plain function, bound to the button object.

In other words, when we execute a method on an object, which is different from the object upon which the method was originally defined, the this keyword no longer refers to the original object, rather to the object that invokes the method.

With reference to our example: we are executing car.getBrand on el (the button element), not the car object, upon which it was originally defined. Consequently, this no longer refers to car, rather to el.

If we want to keep the reference to the original object intact, again, we need to explicitly bind the getBrand() function to the car object by using the bind() method.

el.addEventListener("click", car.getBrand.bind(car));

Now, everything works as expected.

3 this Used Inside Closures

Another instance when this‘s context can be mistaken is when we use this inside of a closure. Consider the following example:

var car = {
  brand: "Nissan",
  getBrand: function(){
    var closure = function(){
      console.log(this.brand);
    };
    return closure();
  }
};

car.getBrand();   // output: undefined

JS Bin

Here, the output we get is undefined, because closure functions (inner functions) don’t have access to the this variable of outer functions. The net result is that this.brand is equal to window.brand, because this in inner functions is bound to the global object.

To fix this issue, we need to keep this bound to the getBrand() function.

var car = {
  brand: "Nissan",
  getBrand: function(){
    var closure = function(){
      console.log(this.brand);
    }.bind(this);
    return closure();
  }
};

car.getBrand();   // output: Nissan

JS Bin

This binding is equivalent to car.getBrand.bind(car).

Another popular method to fix closures, is to assign the this value to another variable, thus preventing the unwanted change.

var car = {
  brand: "Nissan",
  getBrand: function(){
    var self = this;
    var closure = function(){
      console.log(self.brand);
    };
    return closure();
  }
};

car.getBrand();   // output: Nissan

JS Bin

Here, the value of this can be assigned to _this, that, self, me, my, context, an object’s pseudo name, or whatever else works for you. The main point is to keep a reference to the original object.

ECMAScript 6 to the Rescue

In the previous example we saw a primer on what is known as “lexical this“—when we set the this value to another variable. In ECMAScript 6 we can use the similar, but more elegant, technique, applicable via the new arrow functions.

Arrow-functions are created not by the function keyword, but by the so-called “fat arrow” operator (=>). Unlike regular functions, arrow functions take the this value from their immediate enclosing scope. The lexical binding of an arrow function can’t be overridden, even with the new operator.

Let’s now see how arrow function can be used to substitute the var self = this; statement.

var car = {
  brand: "Nissan",
  getBrand: function(){
    // the arrow function keeps the scope of "this" lexical
    var closure = () => {   
      console.log(this.brand);
    };
    return closure();
  }
};

car.getBrand();   // output: Nissan

JS Bin

--ADVERTISEMENT--

What You Need to Remember About this

We saw that the this keyword, like every other mechanism, follows some simple rules, and if we know them well, then we can use that mechanism with more confidence. So, let’s quickly recap what we have learned (from this and from the previous article):

  • this refers to the global object in the following cases:
    • in the outermost context, outside of any function block
    • in functions that are not methods of objects
    • in functions that are not object constructors
  • When a function is called as a property on a parent object, this refers to the parent object.
  • When a function is called using call() or apply(), or bind(), this refers to the first argument passed to these methods. If the first argument is null or not an object, this refers to the global object.
  • When a function is called with the new operator, this refers to the newly created object.
  • When an arrow function (introduced in ECMAScript 6) is used, this relies on lexical scope and refers to the parent object.

Knowing these straight and simple rules, we can easily predict what this will point to, and if it’s not what we want, we know which methods we can use to fix it.

Summary

JavaScript’s this keyword is a tricky concept to master, but with enough practice, master it you can. I hope that this article and my previous article, serve as good basis for your understanding and prove to be a valuable reference the next time this is causing you headaches.

Login or Create Account to Comment
Login Create Account
Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.