JavaScript's "this" vs Python's "self"
00:00
In the last three lessons, I’ve been covering various quirks and gotchas of the JavaScript language. This is the last lesson devoted to quirks, and the whole lesson is devoted to a single thing: the calling context of functions and the this
keyword.
00:15
In previous lessons, I’ve used the this
keyword within the context of functions inside of objects. The concept is similar to the use of self
inside of Python.
00:25
If you’ve ever had the thought, “Why do I have to explicitly define self
? Why can’t it be done magically for me?” Well, the rest of this lesson will make you swear off magic.
00:35
The problem with this
is how it magically gets set. Unlike Python’s self
that is explicitly passed into an object method, the value of this
is based on how the function it is contained within is called.
00:50 It gets complicated when you start getting functions inside of functions, and JavaScript frequently does this. To further complicate things, the arrow function behaves differently. It’s actually much cleaner but mixing and matching it in code can cause surprising results.
01:07
On this slide, you’ll see the this
keyword used in three different situations. First off, I’ve assigned it to the global variable x
.
01:16
This highlights how this
is different than self
in Python. self
is always within the context of an object. In JavaScript, the context is based on the calling function. If there is no calling function, then this
means the global context. Inside of a browser, this is the window
context, but similar idea.
01:39
In a global-level function such as print()
, the context of this
is the global
context.
01:46
The third segment here is an object called a john
. This is similar to how you’ve seen me use this
before. Within this context, the value of this
is the containing object.
01:58
It may be helpful to think about these three different contexts by considering what is calling the function. To call the .cleese()
method, you write john.cleese()
. Calling the print()
function is actually a short form for calling global.print()
, or window.print()
in a browser.
02:16
The context of this
is what’s on the left of the dot (.
) in the calling statement. So when you call john.cleese()
, this is the john
object.
02:26
When you call print()
, that really means you’re calling global.print()
, and this
is what’s to the left of the dot, which is the global
context.
02:35
This is important to understand before I make it more complicated. In Python, self
is the object. In JavaScript, the context is based on how the function was called.
02:46 This has consequences with nested and anonymous functions.
02:52
Let’s look at context within a more complicated object. The fruit
object has an attribute called .items
that is an array of 'apple'
, 'banana'
, and 'pear'
. There’s also a method called .show()
.
03:06
Within that method, the context of this
is the fruit
object itself. So far, this is the same as the john
object on the previous slide, but here’s where it gets a little messier. Within the .show()
method, I want to iterate through the different fruit in the .items
array.
03:23
A convenient way to do this is using the .forEach()
method on the array. The .forEach()
method takes an anonymous function, which it calls for each item as it is visited in the array.
03:36
Within the context of this anonymous function, the this
keyword is no longer bound to the fruit
object, but to the global
context.
03:45
Think back to the “How is it called?” concept I mentioned previously. What is to the left of the calling dot of an anonymous function? It can’t be fruit
because you’re not inside of the calling function on fruit
.
03:58
Anonymous functions end up in the global
context. The resulting consequence is your this
keyword no longer points to the fruit
object. In fact, without taking additional steps, you have no way of getting at the fruit
context here at all.
04:16
In a past lesson, I introduced you to the arrow function. I explained it as a shortcut for defining functions. Well, it has another important aspect. It treats the this
keyword differently.
04:27
In fact, it doesn’t use it. Within the context of an arrow function, the this
hasn’t changed. It can be accessed, though. Consider this .display()
method. The first this
is the fruit
object, as this is inside the member function .display()
. The difference between the .show()
method and the .display()
method is how I’m calling the .forEach()
.
04:50
This time I’m using an arrow function inside of the .forEach()
. As the arrow function doesn’t rebind the this
value, it will still be whatever value it was before, which is the context of this
inside of the .display()
function, which is the fruit
object.
05:07
This is a really good reason for using arrow functions inside of object methods. It makes the this
behave an awful lot more like self
in Python.
05:17
One word of caution, though. The lack of context in an arrow function can be a problem as well as a solution. Consider this last piece of code. By using the shortcut to define the method .spear()
, you aren’t going to get the value of this
.
05:32
If you stopped the code at this point and examined the value of this
, you’d find it was an empty object. When I first came across the arrow function I wasn’t aware of this difference.
05:43
In fact, at the time, I thought the this
keyword was like self
in Python. A very frustrating afternoon followed by a chat with a friend of mine cleared a bunch of this up. If, like me, your mental model of this
is based on other languages, you can be for some surprises, and not the happy birthday kind but more like the parking ticket kind.
06:03
There are other solutions to the context problem than just the arrow function. A common workaround is to store the value of this
in another variable—a common name for it is that
—within the scope of nested anonymous functions.
06:16
The value of that
will still be available in the original object. Some functions, like .forEach()
on arrays, take a parameter that allows you to set the context. When using .forEach()
, you can pass in the current context of this
and .forEach()
will set that context inside of the anonymous function.
06:35
This is handy, but it isn’t available on all functions. But if you’re using .forEach()
, you can take advantage of it.
06:42
JavaScript provides three built-in functions that allow you to explicitly set the context for a calling function. The first is .apply()
. Use the name of the function you were going to call and then call .apply()
on that function name instead.
06:57
You can pass in context and arguments for the named function. The .call()
built-in is exactly like the .apply()
built-in, but instead of taking an array of arguments it uses multi-argument syntax.
07:11
And finally, the .bind()
built-in takes the named function, binds the given context, and returns a new function. You can then call the new function from anywhere, and this will be set appropriately wherever you are. .bind()
is pretty powerful, but it can make for some hard-to-trace code.
07:28
If the .bind()
happens in a different file or hundreds of lines above, it is easy to forget that the context has been changed and not correctly understand the value of this
when you’re reading the code.
07:40 Everything on this slide feels kind of hackish now that arrow functions exist, and I don’t mean hackish in the “I’m proud of the workaround I’ve found, isn’t this cool?” More of the “I’m ashamed it has come to this,” kind of fashion.
07:54 Well, now. It’s been a long journey. In the last lesson, I’ll wrap up and summarize everything that’s been covered.
Become a Member to join the conversation.