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: