A brief introduction to execution contexts and the value of the ‘this’ keyword in JavaScript
Learn how the value of the ‘this’ keyword is determined in JavaScript
Topics
- The Global Object
- Execution Context (aka the value of this)
- Implicit and Explicit Execution Contexts
- Context Loss
Understanding how the value of the this
keyword is determined is very important, otherwise, you might encounter some unexpected results when executing your programs. However, in order to understand how JavaScript determines the value of this
, we must take a step back and take a look at another important concept before we begin, the global object.
The Global Object
When a JavaScript program runs, it creates an object that is accessible throughout your entire program called the global object. The global object in Node.js is the global
object and when talking about JavaScript in the browser it's the window
object.
Note: For the rest of this article, I’ll continue using Node.js as my programming environment.
The global object has the following characteristics:
- It contains values, methods, and objects that are globally available throughout your program e.g.
parseInt
,Math
,String
,Number
. - Variables declared without
const
,let
, orvar
, and function declarations are added as properties to the global object. - It is used as the implicit execution context for function invocations.
Execution Contexts (Implicit and Explicit)
An execution context (or just context) is the environment a function executes in, which in JavaScript is also known as the value of the this
keyword. There are two types of execution contexts implicit and explicit.
Implicit Execution Context
Implicit execution contexts are set automatically by JavaScript.
1) By itself, the value of this
refers to the global
object
As you can see comparing the value of this
and the global
object using the strict comparison operator evaluates to true
because they are the same object. Also, you can explore the properties available to the global
object using the Object.prototype.getOwnPropertyNames
method.
2) Inside of an object, the value of this
refers to the global
object
When the code above is executed, it will print NaN
to the console.
Surprised?
Because inside of an object the value of this
refers to the global
object when we use this.firstName
and this.lastName
to try to access their values we are not referring to the user
object, we are referring to the global
object, and in the global
object the properties firstName
and lastName
don't exist, so they're set to undefined
, then the expression becomes undefined + undefined
, and since you can't perform mathematical operations on non-number values, NaN
is returned.
So, how can we fix this problem?
The following is very important to remember:
JavaScript determines the value of the
this keyword
for function or method invocations based on HOW the function or method is invoked. Where or when the function or method is defined does not matter.
3) In a method invocation, the value of this
refers to the object used to call the method
Now that we know this new rule, we know that when we refer to the username
method, the value of this
will refer to the user
object.
So, with this new rule, we have a way to fix the problem in the previous code example, we can move the properties this.firstName
and this.lastName
inside of a function expression and return the computed value.
Also, notice that the user.username
method was invoked as a method. This is why the value of this
refers to the user
object. What do you think would happen if we assigned the username
method to a variable and we invoked it as a function?
4) In a function invocation, the value of this
refers to the global object
In order to illustrate this, we’ll just make a minor change. We’ll assign the username
method to a variable and then we'll invoke it to see what happens. Just as a quick aside, we can do this (assign a method to a variable) because functions are first-class values in JavaScript, they are just like any other value, so they can be assigned and passed around.
Because we’re now referring to the global
object, we get the same problem we got earlier.
5) In a function invocation with strict-mode, the value of this
is undefined
Because this
does not reference theglobal
object (it references undefined
), attempting to invoke the getUsername
function will trigger a TypeError: Cannot read property 'firstName' of undefined
when we try to access the firstName
property from undefined
.
Explicit Execution Context
In JavaScript, explicit execution contexts are explicitly set by the developer. It refers to binding the value of the this
keyword using the call
, apply
and bind
methods. Let's go through each of the methods mentioned to learn how it works.
1) Explicitly setting the execution context of a function using the call
method
The call
method calls a function or method with a given execution context and also accepts optional arguments for the calling function/method.
Here’s the pattern for the call
method:
calling a function with an explicit execution context using the call
method:
You can use the call
method to reuse code between constructors:
You can use the call
method to invoke a function without specifying the execution context. When you use call
without specifying the context, the context will be the global
object.
2) Explicitly setting the execution context using the apply
method
The apply method works exactly the same as call
the only difference is the optional arguments are accepted as an array-like object.
Here’s the pattern for the apply
method:
Using the apply
method to explicitly set the execution context of a function
3) Using the bind
keyword to "hard bind" the execution context of a function
The way the bind
method works is different from the call
and apply
method. Thebind
method returns a new function that is permanently bound to the object passed as the first argument to bind
. Therefore, the original function or method is not affected. After you have "hard bound" a function to an object, you cannot "unbind" it, even if you call bind
again on that function or use the call
or apply
methods. Unlike, call
and apply
, the bind
method is not immediately executed.
Example 1: Binding a function and assigning it to a variable
Notice that with bind
now the username
method will always reference the user
object, and since it returns a new function we can assign it to another variable and call that variable later on in our code.
Example 2: A permanently bound function’s execution context cannot be changed
Even though we are calling the fetchData
again with the call
method using the smallData
object, the execution context of the getData
function does not change, because it has been permanently bound. So, what we'll get is two copies of the schittsCreekData.data
printed to the console.
Context Loss
Sometimes functions can lose their execution context (aka the value of this
) during specific scenarios, and when this happens, it is called context loss or it is referred to as a function losing its context. However, it's important to know that a function always has a context, and when we say it's lost, it simply that the context is not what we expect.
I’ll cover some of the most common context loss scenarios:
- When a method is extracted from an object and executed as a function
- When an inner function does not use its surrounding context
- When a function is passed an argument and loses its surrounding context
1) A function can lose its context when a method is copied (extracted) and invoked as a function
When we assign the sergiosTasks.getTasks
method to the printTodaysTasks
variable, the getTasks
method's execution context is lost, and the value of this
now references the global
object. Why?
JavaScript determines the value of the
this keyword
for function or method invocations based on HOW the function or method is invoked. Where or when the function or method is defined does not matter.
When the getTasks
method is assigned to a variable, and invoked as a function, its execution context is the global object., and since the global
object does not have a tasks
property, it returns undefined
. So, when we attempt to access the forEach
method from undefined
a TypeError: Cannot read property 'forEach' of undefined
is thrown.
Solution
To preserve the getTasks
method's context, we can use the bind
method and pass the sergiosTasks
object as the execution context. This will permanently bind the getTasks
method to the sergiosTasks
object, and even if we pass its reference around, its context won't be lost.
2) A function can lose its context when an inner function does not use the surrounding context
Why is the context lost for calculateMonthlyWages
function?
JavaScript determines the value of the
this keyword
for function or method invocations based on HOW the function or method is invoked. Where or when the function or method is defined does not matter.
I hope you’re starting to see a pattern here. Fortunately for us, there are a few solutions for this common problem, and we can preserve the execution context in the following ways:
- Preserve the context using a variable in the outer scope
- Call the inner function with explicit context using the
call
orapply
methods - Permanently bind the execution context using the
bind
method
Solution 1: Preserve the execution context using a variable in the outer scope
We can preserve the execution context by storing the value of this in a variable. Because JavaScript uses lexical scoping the code inside the calculateMonthlyWages
function will have access to it, and the function will be able to return the information we need.
Solution 2: Call the inner function with explicit context (using call
or apply
)
Another solution could be using the call
or apply
methods to explicitly call the calculateMonthlyWages
function with the correct context.
Solution 3: Permanently bind the execution context using the bind
method
We can permanently bind the execution context of the calculateMonthlyWages
function using a function expression and the bind
method.
Solution 4: Using an arrow function
Finally, this might be the best-case scenario for an arrow function. Arrow functions inherit their execution context from their surrounding context. Therefore, the calculateMonthlyWages
function will be able to preserve its context by getting it from the outer getSalaryInfo
method.
3) A function can lose its surrounding context when a function is passed as an argument to another function
Another way we can lose the execution context of a function is by passing it as an argument into another function.
Solution
We could solve this problem in different ways. We could add another parameter to the logTasks
function and pass a reference to the sergiosTasks
object, then inside the logTasks
function, we could use either call
or apply
to call the callback
function using the reference to the sergiosTasks
object as the execution context.
However, this problem can be solved in a more straightforward way using the bind
method to permanently bind the getTasks
method to the sergiosTasks
object.
I know, that was a lot to digest, but guess what? There’s still more. This isn’t an exhaustive list of scenarios, but I hope you now have a better idea of how JavaScript determines execution contexts and how you can manipulate contexts using the call
, apply
and bind
methods.
Key Takeaways
- Global object: the global object is automatically created by JavaScript when a program runs. It is used as the implicit execution context of function invocations.
- Execution context: The execution context refers to the value of the
this
keyword at any given point in time. - Implicit execution context: is automatically set by JavaScript.
- Explicit execution context: is set by the developer using the
call
,apply
, andbind
methods. - How JavaScript determines the value of “this”: JavaScript determines the value of the
this
keyword for function or method invocations based on HOW the function or method is invoked. Where or when the function or method is defined does not matter.
Resources
- Object-Oriented Programming with JavaScript — JS120 (Launch School)
- call, apply and bind functions documentation (Mozilla Developer Network)
- The this keyword (W3 Schools)