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'
Further reading suggestions: