Deep dive into "this" keyword in Javascript

Updated: 03 Nov 2018 – Arrow function section added

this is an interesting, at the same time confusing mechanism in JavaScript. These is nothing really advanced about it, however it can be a bit of a mystery. We will try to demystify it together in this post.

If you are coming from an OOP language such as PHP or Java, then this simply refers the current object. In JavaSript however it is much more than that.

Every time a function is invoked, a new execution context is created and this comes for free with it. The value of this is dependent on the conditions under which the function was created.

Global scope

In JavaScript this keyword is available at the global level, so if you run console.log(this) in a web browser you will get the window object back. In the examples below we will be looking at how JavaScript’s this keyword generally behaves.

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

makeNoise();

In the this example, we are defining and invoking makeNoise() function. Whenever we invoke a function at the global level, this will point to the window object. This way you can also add properties and methods to the window object, and retrieve them elsewhere in the code.

Let’s take a look at another example.

const make = 'VW';
const model = 'Golf';

function getCar() {
  return `${this.make} ${this.model}`;
}

console.log(getCar());

As we already know, when using this at the global scope, it refers to the window object. In our example we are defining two variables make, model and a function getCar() globally. By running the function getCar() and using this inside of it, we are accessing data that is defined in the window object. The output of this function will be ‘VW Golf’.

Gotcha

Note that if you are using strict mode then this will be set to undefined.

Object

Let’s have a look at the example where we are using this inside of an object.

const make = 'BMW';
const model = '318';

const car = {
  make: 'VW',
  model: 'Golf',
  getCar: function() {
    return `${this.make} ${this.model}`
  }
}

console.log(car.getCar());

When using this operator inside of an object we are referring to the same object within which this was called. Essentially, this is a referent to the object in context. When we call method getCar(), we create a new execution context and JavaScript works out what this should be referring / pointing to. In our example we are referring to the car object and its properties. Note that we also defined two variables with the same names as the object properties. This is done to prove that we are not accessing the global scope. We should expect the output to be ‘VW Golf’.

Something else to note here is that we could also access the properties from the getCar() method using car.make and car.model. However this is a very vague way of accessing properties, as there might be other objects called car that we might not be aware off and this could mess things up. It is recommended to use this to let JavaScript know that we want to access this exact object.

Closures

const car = {
  makeModel: {
    make: 'VW',
    model: 'Golf'
  },
  colour: {
    main: 'black'
  },
  getDetails: function() {
    return function() {
      return `Make: ${this.makeModel.make}, Model: ${this.makeModel.model}, Colour: ${this.colour.main}`;
    }
  }
}

const carInfo = car.getDetails();
console.log(carInfo());

This is an interesting one. We have an object literal car with two properties, one is an object and another one is a function that returns a function. This last function should return a concatenated string which gives us information about the car. However when we run the code we get Uncaught TypeError: Cannot read property ‘make’ of undefined. So what’s going on here? Intuitively, one would think that this points to the car object. Do you remember we said at the beginning that when a function is created a new execution context is created as well? This is what is happening in our example. this.makeModel.make is pointing to the global object and looking for it there. But makeModel object does not exist in the global object hence we are getting the error. A way to solve this is to create a variable inside the first function and assign this to it.

const car = {
  makeModel: {
    make: 'VW',
    model: 'Golf'
  },
  colour: {
    main: 'black'
  },
  getDetails: function() {
    const self = this;

    return function() {
      return `Make: ${self.makeModel.make}, Model: ${self.makeModel.model}, Colour: ${self.colour.main}`;
    }
  }
}

const carInfo = car.getDetails();
console.log(carInfo());

Objects are set by reference in JavaScript so we are essentially referencing the original this. Running the updated code should produce: ‘Make: VW Model: Golf Colour: black’.

Let’s have a look at one more example:

const car = {
  makeModel: {
    make: 'VW',
    model: 'Golf'
  },
  colour: {
    main: 'black'
  },
  getDetails: function() {
    return 'Make: ' + this.makeModel.make + ' Model: ' + this.makeModel.model + ' Colour: ' + 
    this.colour.main;
  }
}

const carInfo = car.getDetails;
console.log(carInfo());

Essentially, we are reassigning a method getDetails to a variable and it simply becomes a function. Therefore, the concept of this is no loger valid and it does not refer to the car context anymore. It refers to the global object. We would get undefined in the output. What we can to do here is bind it to the car context.

const car = {
  makeModel: {
    make: 'VW',
    model: 'Golf'
  },
  colour: {
    main: 'black'
  },
  getDetails: function() {
    return `Make: ${this.makeModel.make}, Model: ${this.makeModel.model}, Colour: ${this.colour.main}`;
  }
}

const carInfo = car.getDetails;
const carInfoBound = carInfo.bind(car);
console.log(carInfoBound());

Now this is bound to car and works how we would expect it to work.

Arrow function

If we try to use an arrow function introduced in ES6 in the context of a object, we’ll get a different result. Let’s take a closer look at example below.

const someObj = {
  propOne: 'property one',
  propTwo: () => {
    return this;
  }
}

someObj.propTwo(); // returns Window object

Since an arrow function expression does not have / define its own this, it picks this up from its surroundings (lexical scope) hence we get the window object in the above example as its scope is global.

So to make the above example point to the right object we use a normal anonymous function.

const someObj = {
  propOne: 'property one',
  propTwo: function() {
    return this;
  }
}

someObj.propTwo(); // returns someObj object

It is best to use an arrow function with what’s called in JavaScript a non-method function (subroutine).

const funcExpr = function() {
  this.someVar = 'some var';

  const getSomeVar = () => {
    return this.someVar;
  }

  return getSomeVar;
}

const newFuncExpr = new funcExpr();
newFuncExpr(); // 'some var'