* most likely, I missed something, but I'm sure the comments will tell me this
I am writing this article for my personal needs. It is planned that it will contain the answers to all the questions that students ask me on this topic. If it is useful to someone else - great.
Content.
- Introduction
- Misconceptions about this
- How to determine the value of this
Introduction
The
this
is one of the most confusing features of the JavaScript language. Coming from Java, it was intended to help in the implementation of OOP. I wrote in Java for some time, and I must say that during this time, maybe I once doubted what
this
is equal to in a particular place in the code. In JavaScript, such doubts can arise every day - at least until the moment you learn a few simple but non-obvious rules.
Misconceptions about this
There are several common misconceptions regarding this keyword. I want to quickly dispel them before I get to the point.
this is a lexical context.
This impression often arises among beginners. It seems to them that
this
is an object in which, as properties, all variables in this scope are stored. This misconception stems from the fact that in one particular case, roughly speaking, this is so. If we are at the highest level, then
this
equals
window
(in the case of a regular script in the browser). And as we know, all variables declared at the top level are available as
window
properties.
In general, this is not true. This is easy to verify.
function test(){ const a = " , "; console.log(this.a); } test();
this is the object to which the method belongs
Again, this is true in many specific cases, but not always. The important thing is how the function is called, and not whether it is a property of some object. This is clear even from a very simple logic: suppose the same function is a property of two objects simultaneously.
const f = function(){ console.log(this); } const object1 = { method: f }; const object2 = { method: f };
So which of these objects will be her this?
Important! Even if an object is created using ES6 classes, this does not guarantee at all that its method will always have
this
. Don’t be fooled by the resemblance to classes from “normal” languages.
this is a Jedi technique that, once learned, needs to be used everywhere
If possible, avoid using
this
. The only case when it is fully justified is OOP. In other places, using
this
is not just evil, but usually an overkill confusing the code. In essence,
this
is an additional, “minus the first” argument to the function, which is passed there in a complicated, non-obvious way to the beginner. With high probability, it can be replaced by the usual argument, and it will only get better.
How to determine the value of this
Here I will try to give a rigorous and concise algorithm with which even an inexperienced coder will be able to understand what
this
is equal to in his particular case. More verbose explanations will be hidden under the spoilers, so as not to clutter up the visual space.
- Are we inside a function?
Comment In top-level code (not inside any function), this
always refers to a global object. In the case of a regular script in the browser, this is a window
object. But in general, there are different cases.
- Are we inside the arrow function?
- Yes: the value of
this
is the same as in the function one level higher (i.e. containing this one). Return to the previous step and repeat the algorithm for it. If the function is not contained in any other, this
is a global object. - No: see the next paragraph.
Comment One of the main features of arrow functions is the so-called “lexical this
”. This means that the value of this
in the arrow function is determined solely by where (in what lexical context) it was created, and does not depend on how it was subsequently called. Sometimes this is not what we need, but more often it makes arrow functions very convenient and predictable.
- Is this function called as a constructor (using the new operator)?
- Yes:
this
refers to a new object that is “under construction”. - No: see the next paragraph.
Comment Perhaps it’s worth separately specifying the case when it comes to the constructor of the inherited ES6 class. Then, before calling super()
, this
does not have a value (accessing it will cause an error), and after calling super()
it equals the new object of the parent class.
- Is this function created using the bind ?
- Yes: the value of
this
is equal to the value of the first argument that we passed to the bind
method when creating this function. - No: see the next paragraph.
Comment The bind
method creates a copy of the function, fixing this
and, optionally, the first few arguments for it. In fact, this creates not just a copy of the function, but, I quote, an “exotic BoundFunction object”. Its exoticism is manifested, in particular, in the fact that by a repeated call to bind
we can no longer change this
. Therefore, strictly speaking, the answer in this paragraph should have been formulated as follows: if so, then this
is equal to the first argument of the first call to bind
, which led to the creation of this function.
- Is this function passed somewhere as a callback or handler?
- Yes: the Lord alone knows what this will equal when summoned. Go read the documentation for the thing that will cause it.
- No: see the next paragraph.
Comment For a non-arrow and bound function, the value of
this
depends on the circumstances in which it was called. If you do not call it in person, but pass it somewhere, then
this
value may or may not be substituted with an unknown value to you.
Examples:
`use strict` document.addEventListener('keydown', function(){ console.log(this); });
- This function is called using the apply or call ?
- Yes: in this case,
this
equals the first argument passed to the corresponding method. - No: see the next paragraph.
Comment Another way to explicitly set this
. More precisely, two. However, in terms of this
no difference between apply
and call
, the only difference is how the rest of the arguments are passed.
- Is this function obtained as the value of an object property and is called immediately?
- Yes:
this
equals the above object. - No: see the next paragraph.
Comment Actually, from this mechanism (as well as from experience working with other languages), the legs grow of the belief that "
this
is the object whose method we called." Perhaps I’ll just write the code.
'use strict'; const object1 = { method: function(){ console.log(this); } } const object2 = { method: object1.method } object1.method();
- Does the code execute in strict mode? ('use strict', ES6 module)
- Yes:
this
equals undefined
. - No:
this
is equal to a global object.
Comment If we get to this point, then
this
not set by any of the mechanisms that allow it to be set. There are various misconceptions about how else this can be passed. For example, during interviews they often tell me this kind of thing:
const obj = { test: function(){ (function(){ console.log(this); })();
Or, as I said in the “misconceptions” section, many people think that if a function is a method of an object created using ES6 classes, then this in it will always be equal to this object. This is also not true.
So, if we get to this point, it means that the other circumstances of calling our function do not matter. And it all comes down to whether we are in strict mode or not.
Historically, as a "default"
this
, a global object was passed to such functions. This approach was later found unsafe. ES5 introduced a strict mode that fixes many problems of earlier versions of ECMAScript. It is included with the 'use strict' directive at the beginning of a file or function. In this mode, the "default" value of
this
is
undefined
.
In ES6 modules, strict mode is enabled by default.
There are also other mechanisms for including strict mode, for example, in NodeJS, strict mode for all files can be enabled with the
--use-strict
flag.
That, in general, is all. Determining the value of
this
, of course, is less simple than we would like, but also not as difficult as it might seem. Learn this algorithm as a multiplication table - and you will never have problems with
this
again. So it goes.
PS User
Aingis suggested that when using the
with construct, the object passed into it as a context replaces the global object. Perhaps I will not enter this into my classifier, because the chance to meet
with
in 2019+ is rather small. But in any case, this is an interesting point.
PPS User
rqrqrqrq suggested that
new
higher priority than
bind
. The corresponding revision to the classifier has already been made. Thank!