This book is a work in progress. Content may be updated as the source material evolves.
this keyword used quite a bit so far, but haven’t really dug in to understand exactly how it works in JS. It’s time we do so.
But to properly understand this in JS, you need to set aside any preconceptions you may have, especially assumptions from how this works in other programming languages you may have experience in.
Here’s the most important thing to understand about this: the determination of what value (usually, object) this points at is not made at author time, but rather determined at runtime. That means you cannot simply look at a this-aware function (even a method in a class definition) and know for sure what this will hold while that function runs.
Instead, you have to find each place the function is invoked, and look at how it’s invoked (not even where matters). That’s the only way to fully answer what this will point to.
This Aware
I used the phrasethis-aware just a moment ago. But what exactly do I mean by that?
Any function that has a this keyword in it.
If a function does not have this in it anywhere, then the rules of how this behaves don’t affect that function in any way. But if it does have even a single this in it, then you absolutely cannot determine how the function will behave without figuring out, for each invocation of the function, what this will point to.
It’s sort of like the this keyword is a placeholder in a template. That placeholder’s value-replacement doesn’t get determined when we author the code; it gets determined while the code is running.
This Confuses Me
Now, in fairness, that’s already partially true if we consider a function’s parameters. To understand how a function is going to work, we need to know what is being passed into it. So any function with at least one parameter is, in a similar sense, argument-aware. But with parameters, we often have a bit more of a hint from the function itself what the parameters will do and hold. We often see the names of the parameters declared right in the function header, which goes a long way to explaining their nature/purpose. Actually,this is very much like a parameter to a function, but it’s an implicit parameter rather than an explicit one. You don’t see any signal that this is going to be used, in the function header anywhere. You have to read the entire function body to see if this appears anywhere.
So What Is This?
Ifthis is an implicit parameter, what’s its purpose? What’s being passed in?
Hopefully you have already read the “Scope & Closures” book of this series. In that book, I explained at length how scopes (and closures!) work, an especially important characteristic of functions.
Lexical scope (including all the variables closed over) represents a static context for the function’s lexical identifier references to be evaluated against. It’s fixed/static because at author time, when you place functions and variable declarations in various (nested) scopes, those decisions are fixed, and unaffected by any runtime conditions.
By contrast, a different programming language might offer dynamic scope, where the context for a function’s variable references is not determined by author-time decisions but by runtime conditions.
JS scope is always and only lexical and static (if we ignore non-strict mode cheats like
eval(..) and with). However, one of the truly powerful things about JS is that it offers another mechanism with similar flexibility and capabilities to dynamic scope.this mechanism is, effectively, dynamic context (not scope); it’s how a this-aware function can be dynamically invoked against different contexts — something that’s impossible with closure and lexical scope identifiers!
Can We Get On With This?
So why have I belabored this subject for a couple of pages now? You get it, right!? You’re ready to move on. My point is, you the author of code, and all other readers of the code even years or decades in the future, need to bethis-aware. That’s the choice, the burden, you place on the reading of such code. And yes, that goes for the choice to use class (see Chapter 3), as most class methods will be this-aware out of necessity.
Let me put it this way: don’t use this-aware code unless you really can justify it, and you’ve carefully weighed the costs. Just because you’ve seen a lot of code examples slinging around this in others’ code, doesn’t mean that this belongs in this code you’re writing.
The this mechanism in JS, paired with [[Prototype]] delegation, is an extremely powerful pillar of the language. But as the cliche goes: “with great power comes great responsibility”.
This Is It!
OK, enough of the wordy lecture. You’re ready to dive intothis code, right?
Let’s revisit (and extend) Point2d from Chapter 3, but just as an object with data properties and functions on it, instead of using class:
init(..), rotate(..), and toString() functions are this-aware. You might be in the habit of assuming that the this reference will obviously always hold the point object. But that’s not guaranteed in any way.
Keep reminding yourself as you go through the rest of this chapter: the this value for a function is determined by how the function is invoked.
Implicit Context Invocation
Consider this call:init(..) function, but notice the point. in front of it? This is an implicit context binding. It says to JS: invoke the init(..) function with this referencing point.
That is the normal way we’d expect a this to work, and that’s also one of the most common ways we invoke functions. So the typical invocation gives us the intuitive outcome. That’s a good thing!
Default Context Invocation
But what happens if we do this?this assignment works.
The call-site for the function is init(3,4), which is different than point.init(3,4). When there’s no implicit context (point.), nor any other kind of this assignment mechanism, the default context assignment occurs.
What will this reference when init(3,4) is invoked like that?
It depends.
Uh oh. Depends? That sounds confusing.
Don’t worry, it’s not as bad as it sounds. The default context assignment depends on whether the code is in strict-mode or not. But thankfully, virtually all JS code these days is running in strict-mode; for example, ESM (ES Modules) always run in strict-mode, as does code inside a class block.
So almost all of the time, modern JS code will be running in strict-mode, and thus the default assignment context won’t “depend” on anything; it’s pretty straightforward: undefined. That’s it!
Keep in mind:
undefined does not mean “not defined”; it means, “defined with the special empty undefined value”.init(3,4), if run in strict-mode, would throw an exception. Why? Because the this.x reference in init(..) is a .x property access on undefined (i.e., undefined.x), which is not allowed:
undefined, so that any default context invocation of a this-aware function will fail with such an exception?
Because a this-aware function always needs a this. The invocation init(3,4) isn’t providing a this, so that is a mistake, and should raise an exception so the mistake can be corrected. The lesson: never invoke a this-aware function without providing it a this!
Explicit Context Invocation
Functions can alternately be invoked with explicit context, using the built-incall(..) or apply(..) utilities:
init.call(point,3,4) is effectively the same as point.init(3,4), in that both of them assign point as the this context for the init(..) invocation.
Both
call(..) and apply(..) utilities take as their first argument a this context value; that’s almost always an object, but can technically can be any value (number, string, etc). The call(..) utility takes subsequent arguments and passes them through to the invoked function, whereas apply(..) expects its second argument to be an array of values to pass as arguments.anotherPoint, but I didn’t want to repeat the definitions of those init(..) / rotate(..) / toString() functions from point. So I “borrowed” a function reference, point.init, and explicitly set the empty object anotherPoint as the this context, via call(..).
Any this-aware functions can be borrowed like this: point.rotate.call(anotherPoint, ..), point.toString.call(anotherPoint).
New Context Invocation
We’ve so far seen three different ways of context assignment at the function call-site: default, implicit, and explicit. A fourth way to call a function, and assign thethis for that invocation, is with the new keyword:
new used with class for creating instances. But as an underlying mechanism of the JS language, new is not inherently a class operation.
In a sense, the new keyword hijacks a function and forces its behavior into a different mode than a normal invocation. Here are the 4 special steps that JS performs when a function is invoked with new:
Link the prototype
Link the
[[Prototype]] of that new empty object to the function’s .prototype object (see Chapter 2).Review This
We’ve seen four rules forthis context assignment in function calls. Let’s put them in order of precedence:
Implicit context
Is the function invoked with an object reference at the call-site (e.g.,
point.init(..)), implicitly setting this?this for a function invocation. If multiple rules match a call-site (e.g., new point.init.call(..)), the first rule from the list to match wins.
An Arrow Points Somewhere
Everything I’ve asserted so far aboutthis in functions, and how its determined based on the call-site, makes one giant assumption: that you’re dealing with a regular function (or method).
So what’s an irregular function?!?
Here’s a real example of an => arrow function:
this reference, and whether the first => form differs from the others (hint: it does!).
Lexical This
The name for this pattern, by the way, is “lexical this”, meaning athis that behaves like a lexical scope variable instead of like a dynamic context binding.
But it turns out JS has an easier way of performing the “lexical this” magic trick. Are you ready for the trick reveal!?
The => arrow function! Tada!
That’s right, the => function is, unlike all other function forms, special, in that it’s not special at all. Or, rather, that it doesn’t define anything special for this behavior whatsoever.
In an => function, the this keyword… is not a keyword. It’s absolutely no different from any other variable, like context or happyFace or foobarbaz.
Let me illustrate this point more directly:
innerFn.call(two) would, for any regular function definition, have resulted in "sad face" here. But since the inner function we defined and returned (and assigned to innerFn) was an irregular => arrow function, it has no special this behavior, but instead has “lexical this” behavior.
When the innerFn(..) (aka inner(..)) function is invoked, even with an explicit context assignment via .call(..), that assignment is ignored.
When a
this is encountered (this.value) inside an => arrow function, this is treated like a normal lexical variable, not a special keyword. And since there is no this variable in that function itself, JS does what it always does with lexical variables: it goes up one level of lexical scope — in this case, to the surrounding outer(..) function, and it checks to see if there’s any registered this in that scope.Back To The… Button
A more direct and appropriate way of solving our earlier issue, where we had donevar context = this to get a sort of faked “lexical this” behavior, is to use the => arrow function, since its primary design feature is… “lexical this”.
This Is Bound To Come Up
Backing up a bit, there’s another option if you don’t want to use an=> arrow function’s “lexical this” behavior to address the button event handler functionality.
In addition to call(..) / apply(..) — these invoke functions, remember! — JS functions also have a third utility built in, called bind(..) — which does not invoke the function, just to be clear.
The bind(..) utility defines a new wrapped/bound version of a function, where its this is preset and fixed, and cannot be overridden with a call(..) or apply(..), or even an implicit context object at the call-site:
this-bound function as the event handler, it similarly doesn’t matter how that utility tries to set a this, because I’ve already forced the this to be what I wanted: the value of this from the surrounding function invocation context.
Hardly New
This pattern is often referred to as “hard binding”, since we’re creating a function reference that is strongly bound to a particularthis. A lot of JS writings have claimed that the => arrow function is essentially just syntax for the bind(this) hard-binding. It’s not.
Let me illustrate:
new f() call. That’s admittedly a strange usage, to call new on a hard-bound function. Even though f() was hard-bound to a this context of obj, the new operator was able to hijack the hard-bound function’s this and re-bind it to the newly created and empty object.
But… what’s going to happen with the new g() call, which is invoking new on the returned => arrow function?
That line will actually throw an exception, because an => function cannot be used with the new keyword.
But why? My best answer is that conceptually and actually, an => arrow function is not a function with a hard-bound this, it’s a function that has no this at all. As such, new makes no sense against such a function, so JS just disallows it.
Why This?
So why have I belabored this subject for an entire chapter? Because understandingthis is fundamental to understanding how objects work together in JavaScript. The this mechanism, paired with [[Prototype]] delegation, gives us tremendous flexibility in how we structure and compose our programs.
But with that flexibility comes responsibility. You need to understand how this works, how it’s assigned, and when to use it (and when not to).
In the next chapter, we’ll see how this pairs with prototypes to create another pattern of code organization: delegation.
