To understand how this works, we must first locate the problem, then solve it, and at the very end come to some conclusions.
In that case, I have no other choice than to invite you to a lecture in which we will explain everything necessary and get to a conclusion on how to avoid the use of this keyword.
First time with "this"
Take a look at the following code, which is designed to create a specific list of n elements using the generic List class:
The code in this case will behave as we expect. This will point to instances of the class that is, in this case, the object created with new.
So can we assume that this is a kind of variable that stores references to an object? This is true, but not exactly. It's not that simple...
The first problem with "this"
Imagine that a project manager comes in and says that deletion is supposed to be generic - a typical project situation (change in requirements). You don't know JS particularly well. The first thing that comes to your mind is to pass a callback to the remove function.
So let's change the implementation of the List class:
After trying to run the code, you will get something like this on your console: Cannot read properties of undefined (reading 'elementIdxToRemove').
Well, but how! This was supposed to point to an instance of the class! Well, and it does point. Unfortunately not to the class. In this case, it points to undefined.
What matters is how our removeUser function is called. For function declarations passed as an argument, this has the value undefined. This is simply how it is implemented. Every technology has some default behaviour.
Rule 1: Function declarations passed as callbacks have this set as undefined.
How to fix it?
We need to assign the value of this to what it should be in the context of our code, that is, an instance of the UsersList class. After this procedure, it will start working properly.
How to achieve it?
The simplest way will be to use the arrow function, which will assign the value of this based on the nearest lexical context - in our case, the object created by the UsersList class, or more precisely, this of that object.
Rule 2: Arrow functions assign a value of this, to the this of whatever is in the nearest lexical context - even if it's passed as a callback.
What is the lexical context?
In the lexical context, the scope is determined by where the variable is declared in the code.
This means that the variable is available only inside the block, function or module in which it is declared and in all nested blocks and functions.
The closest in lexical context for the outerThis function is the window object. In contrast, for the innerFunction function, it is the iHaveMyOwnThis object.
So why did console.log for outerThis showed undefined and not a window?
And this is where another rule comes into play:
Rule 3: For use strict mode, the value of this when pointing to a global object adopts the undefined value.
This can be something different depending on the environment in which our code is running. In the case of a browser, it is a window, and in the case of Node.js it may be a module.
It has been implemented this way to avoid unpleasant surprises when referring to object properties. It may turn out that this.nameMethod() will turn out to be the one belonging to the window object.
Thanks to it, the developer is able to catch a potential problem at the moment of running the code - he immediately sees an exception when trying to refer to this, which can point to a global object.
Implicit binding of "this"
In the above example inside the bike function, we use this to log the name value. When we assign the bike function declaration to any object then that object becomes this.
Rule 4: If you assign a function declaration that uses this to an object, that object becomes this for that function.
How will the code behave when we replace the bike function declaration with an arrow function?
For both cases we get an exception: Cannot read properties of undefined (reading 'name)'. Same reason as in the first example. The variable this points to a window object, which by use strict has the undefined value.
"This" in classes
If we create an object using new, then this will have the value of the created object. Therefore, in the following code, we have no problems.
If we use the notation with the class and use new, the effect will be the same. The difference will be only in the syntax.
Rule 5: When using new, the value of this will refer to the created object - the syntax (whether a class or a function as a constructor) does not matter here.
What about inheritance? What guarantee do we have that we won't mess something up by referring to this in the Vehicle class? To prove it, we'll pass this as an argument to the repair method, and then check how the result behaves based on different call locations and based on different arguments.
In the first console.log we have 2x the value true. It shows that by referring to this inside the Bike and Vehicle classes, we are sure that this for the parent and child classes is the same.
In the third case, we have true. We passed a bike object, and we already know that after using new, the value of this points to an instance of an object. Also, we know that this of the parent and child classes points to the same thing. In that case, this in the context of two mentioned classes is the same as the created bike object that we passed as an argument to its own repair method.
Rule 6: With inheritance, this refers to the same thing in the parent and child classes (to the same object instance).
"Static" keyword and "this"
Here is a little more interesting. Take a look at the code below:
In the case of fields and static methods, this takes the value of a class, not an instance of an object - it's not there yet and will not be. After all, it's a static method.
Rule 7: For static fields, this refers to the class.
Explicit binding of "this"
Is it possible to set this up ourselves?
Of course, it is!
Most functions allow you to pass something described as thisArg. In the above example, we called the Bike function using call. Next, we passed an obj object as this.
Later we did the same with apply, which allows us to call functions with multiple arguments. In other words, we controlled the binding of this.
This can even be done for functions such as forEach:
Rule 8: We can set the value of this explicitly using the call or apply method or by passing the additional argument thisArg to methods such as forEach.
Hard/fixed binding of "this"
It can be done in yet another way. There is a special bind method that allows us to set the value of this not at the time when the function is called, but at any time. Instead of keeping an eye on the value of this for each call, we can do it only once - in the constructor.
Rule 9: We can set this using the bind method, which creates a copy of the function and allows you to pass your own this.
Getting rid of "this"
We can avoid referring to this simply by writing the code in functional way. Take a look at the example below, in which we converted object-oriented code to a functional approach and we removed this entirely.
Instead of referring to this, now we rely on the closure mechanism. If you are interested in how it works, feel free to check this article.
If you want to explore object-oriented programming then you need to check also this post.
Rule 10 (thank goodness probably the last one): We can remove this from our code using functional programming and the closure mechanism.
The second option, a little more invasive, is to change the paradigm to a functional one.
The whole concept of this is another element of the language that sooner or later you need to understand thoroughly to write code consciously and not expose yourself to numerous problems.
If you enjoyed it, be sure to visit us on Linkedin where we regularly upload content from programming.
Check out the explanation of that concept in the documentation on which this entry is based.
Special thanks to reader Jacek Piętal for his help.