The this
keyword in JavaScript made simple.
Execution context and how the value of this
is determined.
The idea behind the this
keyword is that when you invoke a method that contains the this
keyword in its body, this
points to an object and tells the method where to look to find the properties needed in the method. The value of this
can also be referred to as the execution context of a function or method.
What you need to know:
- Outside of a function body,
this
points to the global object. - Regular function calls use the global object as their execution context.
- Method calls use the calling object as their execution context.
- How
this
is bound depends on how a function is invoked rather than on where it’s defined.
Let’s examine each of those in more detail:
Outside of a function
The most important thing to know when determining the value of this
in JavaScript is that if this
appears outside of the body of a function or method, it points to the global object.
The global object is an object that always exists on the global scope or the top level of your code. Object
, Array
, Function
and other built-in constructors are all properties of the global object.
Inside of a function
When you invoke a regular function that contains the this
keyword in its body, this
points to the global object.
this
points to the global object inside a function, but in strict mode it points toundefined
.
Example
In the following code block the function printString
will look for the properties subject
and adjective
. So based on what we have just learned, what will happen when we invoke the function printString
below?
Why did this.subject
and this.adjective
evaluate to undefined
? As printString
was invoked as a standalone function, its execution context or this
binding was implicitly set to the global object.
So printString
looked at the global object, searched for a property named subject
, and as there is no property named subject
on the global object it evaluated to undefined
. Then the same thing happened with this.adjective
.
To prove that is what’s happening, we can add those two properties to the global object and see what happens.
Notice that on lines 5 and 6 we add the properties subject
and adjective
to the global object. Now when we invoke printString
on line 8, it finds those properties and appends their values to the string.
Note: adding properties to the global object is not recommended. I’ve done it here to illustrate a point. Don’t do this in your code.
Also, note that the above code will not work in strict mode. Remember that in strict mode, this
will point to undefined
in a regular function call.
Summary:
- If
this
appears outside of the body of a function, it points to the global object. - Regular function calls use the global object as their execution context.
Using this
in methods
So far we have seen how this
is bound in a standalone function but that’s not very useful. When this
really comes into play is when we use it in object methods. Let’s add our printString
function to an object and call it as a method.
In this example, the method printString
looks for two properties, subject
and adjective
. How does it know where to look for them?
You may think that it just looks for those properties in the same object that the method appears in. That is not quite accurate though. Instead, it looks for the properties in the object that calls the method.
That is a very important distinction. What would happen if we stored object.printString
in a variable and then called it?
Notice that we have stored object.printString
in a variable called func
and then invoked func
on line 10. Now suddenly this
is no longer pointing to object
. Can you guess where it is pointing?
If you said the global object, you were correct. func
is called as a standalone function on line 10 and not as a method, and as we learned earlier the implicit execution context of a function is the global object.
This teaches us something very important about the binding of this
. The value of this
is determined, not by where it appears in your code, but by how a function or method is invoked. Let’s see how we can get func
working correctly again.
Summary:
- Method calls use the calling object as their execution context.
- You can’t tell the binding of this just by looking at the body of a function or method, you must look at how it is invoked.
Explicitly setting the context using call
So far we have mentioned several times how this
is implicitly set when you invoke a function or method. That implies that we can also explicitly set the execution context.
All functions have access to a property method named call
that they inherit from Function.prototype
. We can invoke the call
method on a function and explicitly set the execution context to whatever object we pass in as the first argument.
Notice that we invoke the func
function on line 15 with the call
method, and we pass in anotherObject
as an argument. Now the execution context of func
on line 15 is bound to the object anotherObject
, and so it looks there for the properties subject
and adjective
.
Note that using call
does not mutate func
, so we can still call func
as a standalone function and its execution context will still be implicitly bound to the global object. But what if we wanted to permanently bind anotherObject
as the execution context of func
?
Permanently binding an object
All functions also have access to a property method named bind
that they inherit from Function.prototype
. bind
works differently from call
in that it doesn’t invoke the function. Instead, it returns a new function with its execution context permanently bound to the passed in argument.
Notice how on line 15 we store the new function returned by bind
to the variable newFunc
.
When we invoke newFunc
on line 16, we see that its execution context has been bound to anotherObject
. It’s no longer possible to change the execution context of newFunc
as it has been permanently bound to anotherObject
. Note that func
has not been mutated.
Summary:
- We can explicitly set the execution context using
call
orbind
. call
invokes the function with its execution context set to the passed in argument.bind
returns a new function with the execution context permanently bound to the passed in argument.
Why arrow functions are special
Now that you’ve taken all this time to learn how this
is bound inside in functions, so you may be surprised to learn to arrow functions do not have their own this
binding.
Notice that in the following code block, we’ve replaced our printString
method with an arrow function.
When invoked, this.subject
and this.adjective
evaluate to undefined
. This is because arrow functions do not have their own this
binding, instead, they establish the binding of this
from the surrounding code.
We know that when this
is not in a function body, it points to the global object (or undefined
in strict mode). Therefore, in this case, this
points to the global object.
Once an arrow function is defined in your code, its execution context cannot be changed. If the arrow funtion is defined within the body of another function, it will always have the same context as the outer function.
Unlike with non-arrow functions, the execution context for arrow functions is determined lexically, which means that we determine the context from where the arrow function is defined rather than from how it is invoked.
As arrow functions do not get their own this
binding, the methods call
and bind
will not work.
This may sound like a negative, but it is actually a very useful design feature as we will now see.
Dealing with context loss using Arrow functions
Let’s say we want to use an array method in our object within the printProps
method. Notice on line 5 that we call forEach
on anArray
and we try to print out the elements of anotherArray
.
Why did we get a TypeError
? It turns out that when we pass a function as an argument to another function, we lose access to this
in the inner function. This is called ‘context loss’. So when we pass an anonymous function as an argument to forEach
on line 5, this
on line 6 points to the global object rather than to object
.
There are a few ways to fix this including using bind
, but this is where arrow functions really shine! In the following example, we change our anonymous function to an arrow function on line 5:
Now it works! Why? Remember, arrow functions do not get their own this
binding, instead they use this
from the surrounding code. As the arrow function is in the body of printProps
, it uses the this
binding of printProps
which we know to be object
.
So while arrow functions are not suitable for use as methods, they can be very useful when we need a function within a function or when we need to pass a function as an argument to another function.
Summary
- Execution context refers to the value of the
this
keyword. - Outside of a function body,
this
points to the global object. - Regular function calls use the global object as their execution context.
- Method calls use the calling object as their execution context.
- In strict mode,
this
is implicitly set toundefined
rather than the global object within regular function calls. - Arrow functions do not have their own execution context, they use the context of their surrounding scope.
- You can’t tell the binding of
this
just by looking at the body of a function or method, you must look at how it is invoked.
Thanks to Launch School where I studied all of this in far more detail. Feel free to check out some of my other articles on JavaScript and my journey through Launch School here.