Javascript. The “this” variable
An introduction to the variable “this” and how it takes its value
The variable this
represents one of the main headaches for developers starting to learn Javascript. Unlike other languages where its value is very intuitive, in Javascript it is necessary to know a series of rules in order to understand the value that this variable will finally adopt.
In order to help make the process of familiarizing ourselves with this variable much easier, I have written this article where I will analyze the main rules that govern the use of this
in Javascript.
I hope it works for you!
The global execution environment
To begin to understand the variable this
, the first thing we must do is refer to the execution environment of our application.
For example, if we are executing Javascript in a browser, this environment will be represented by the window
object itself, so by default this
will have this value:
window === this; // true
On the other hand, if we are working with NodeJS the environment will be represented by the global
variable so:
global === this; // true
That is, this
is much more related to the execution environment or context than to the concept of this same variable in object-oriented programming, where this
represents the object itself.
Functions
Unlike object-oriented programming where only objects have the variable this available, in Javascript we can access this
variable within functions.
As a general rule, the value in these cases will be associated with the object that invoked the function. What does this mean? Let’s see it with some examples.
Suppose we define the following function in our browser:
function foo() {
console.log(this);
}
And we invoke it from the console itself:
foo(); // Window
What we will get by printing this
variable will be the window
object. Why? Because the object that invokes the foo()
function is, by default, the global window
object.
The same will happen if for example we write the following function:
function Person(name, email) {
this.name = name;
this.email = email;
}const person = Person('Gerardo', 'gerardo@latteandcode.com');
console.log(person);
In this case, what we will get per screen will be undefined
. Why? For the same reason as before. Person
is a function that is being invoked by the global window
object, so by doing this.name
assignment what we are really doing is window.name = name
.
To achieve the desired effect we will have to resort to the new
operator, so that, now, we have a person
object with its set name
and email
properties:
function Person(name, email) {
this.name = name;
this.email = email;
}const person = new Person('Gerardo', 'gerardo@latteandcode.com');
console.log(person);
New
In Javascript we can add the new
operator in front of the invocation of a function to convert this invocation into a constructor. It was what we did in the previous example when we called the Person
function:
const person = new Person('Gerardo', 'gerardo@latteandcode.com');
console.log(person);
That is, the new
operator allows the creation of a new object that becomes the this
of that invocation and that is implicitly returned by the invoked function (unless we explicitly return something else within the function using return
).
Methods
Okay, and what happens when we invoke a function that belongs to an object? That is, what value does this
take when we invoke the method of an object?
Overall, it’s what we expect:
const person = {
name: 'Gerardo',
surname: 'Fernández',
fullname: function() {
console.log(`${this.name} ${this.surname}`);
}
};person.fullname(); // Gerardo Fernández
In other words, when we invoke the fullname()
function, the variable this
is the person
object and that is why we can access its name
and surname
properties. This is because it is the person
object that is invoking the fullname
method.
But if for example we change it to the following:
const person = {
name: 'Gerardo',
surname: 'Fernández',
fullname: function() {
return function() {
console.log(`${this.name} ${this.surname}`);
};
}
};person.fullname()(); // undefined
What we get is undefined
in the console. Why? Let’s look at it in another way:
name = 'Chrome';
surname = 'Window';const person = {
name: 'Gerardo',
surname: 'Fernández',
fullname: function() {
return function() {
console.log(`${this.name} ${this.surname}`);
};
}
};const printFullname = person.fullname();
printFullname(); // Chrome Window
That is, the object that is invoking the function returned by the fullname
method of the person
object is the global object as it happened at the beginning of the article and, therefore, this
has the value of the global object (window
if we are working with the browser console ).
Bind, call and apply
Therefore, this
may lose the reference depending on the object that calls the function. To prevent this, there is the possibility of using the bind
, call
y apply
functions in order to force the value of this
in the invocation.
For example, in the previous example we can use bind
to return a function whose value for this
is the object itself:
const person = {
name: 'Gerardo',
surname: 'Fernández',
fullname: function() {
const printFullname = function() {
console.log(`${this.name} ${this.surname}`);
};
return printFullname.bind(this);
}
};const printFullname = person.fullname(); // bound
printFullname(); // `Gerardo Fernández`
In this case the variable this inside the printFullname
function will already be “always” bound to the person
object.
Another way is to resort to the call
and apply
functions that have a similar operation allowing us to explicitly specify the value of this
.
const book = {
currentPage: 1,
gotoPage: function(page) {
this.currentPage = page;
console.log(`Current page: ${this.currentPage}`);
}
}const gotoPage = book.gotoPage;gotoPage.call(book, 10); // Current page: 10
gotoPage.apply(book, [10]); // Current page: 10
Note. The difference as you can see between call
and apply
is the way of specifying the arguments with which the function will be invoked: either individually or contained within an array.
Arrow functions
With the arrival of ES6, a new way of declaring functions was introduced that directly affects the value taken by this
: the “arrow functions”.
These types of functions take the value of this
from the execution context that surrounds them regardless of the way the function is invoked and preventing that apply
, call
or bind
modify it.
In Javascript every time a function is executed an execution context is created.
However, the “arrow functions” do not in themselves define an execution context, so they take it from the immediately superior one.
Let’s see it with an example:
const book = {
currentPage: 1,
readPage: function() {
setInterval(function() {
this.currentPage += 1;
console.log(this.currentPage);
}, 1000);
}
}book.readPage();
❗❗ What do you think the above code prints on screen? Unfortunately “a succession of Nan
since the” callback “of the setInterval
function is executed in the global context, that is, there where the value of this
is window
. So that you visualize it it is as if internally Javascript was doing the following:
var callback = function() {
this.currentPage += 1;
console.log(this.currentPage);
}callback(); // cada 1 segundo
However, the “arrow functions” allow us to avoid this problem:
const book = {
currentPage: 1,
readPage: function() {
setInterval(() => {
this.currentPage += 1;
console.log(this.currentPage);
}, 1000);
}
}book.readPage();
Since the “arrow functions” take their context from the one immediately above, the value of this
will be what the readPage
function has. Since we are invoking the readPage
method in the following way: book.readPage
, the value of this
inside the method will be that of the book
object and this
this in the “arrow function”.
But beware, if we did the following:
const book = {
currentPage: 1,
readPage: function() {
setInterval(() => {
this.currentPage += 1;
console.log(this.currentPage);
}, 1000);
}
}const readPage = book.readPage;
readPage();
We would receive only Nan
per screen, since the context of readPage
would be global.
Classes
The classes in Javascript were also an addition to ES6 with the aim of making the transition from object-oriented programming languages easier. No, before 2015 there were no classes in Javascript. 😅
A class in Javascript generally contains a constructor
method where the value of this
refers to the object being created. However, within the methods of the this
object it follows the same rules that we have seen previously:
class Person {
constructor(name, surname) {
this.name = name;
this.surname = surname;
}printFullname() {
console.log(`${this.name} ${this.surname}`);
}
}const person = new Person('Gerardo', 'Fernández');
person.printFullname(); // Gerardo Fernández ✅const foo = person.printFullname;
foo(); // Uncaught Type Error cannot read 'name' of undefined ❌
As you can see, when working with classes we don’t get undefined
when we store a reference to the function’s method. This is because the classes run in strict mode
, so the value for this
in that invocation for which no value for this
has been defined is undefined
.
This is why in the past, React used to bind the callbacks associated with events in the constructor:
Final thoughts
As you have seen, the variable this
can sometimes be a headache, especially in those moments when we lose the reference to its value as when we work with “event listeners” or timing functions.
That is why I found it interesting to write this article summarizing the main cases so that next time it will be easier for you to identify their value.
References
Do you want to read more articles like this?
If you liked this article I encourage you to subscribe to the newsletter that I send every Sunday with similar publications to this and more recommended content: 👇👇👇