JavaScript execution context — from compiling to execution (part 1)
To many, JavaScript is a mystery. It has unique characteristics.
Knowing it, you probably hear of the following terms:
- Hoisting
- Scope and scope chain
- Closure
- this
All of them have “weird” behaviors that are unique in JavaScript.
The key to demystifying the concepts is the execution context. I hope these posts can offer a different perspective to understand JavaScript instead of copying the accurate but obscure definition from MDN.
Don’t get me wrong. The definition of a concept is useful. I read descriptions from MDN. However, I believe that understanding How is more critical than memorizing What.
At the end of this series, you probably will come up with your definition of all these terms, which make more sense to you.
So, this information becomes your knowledge.
Two steps: compiling and execution
When a piece of JavaScript codes is run, we are talking about two steps: compiling and execution.
It looks simple. However, all mysteries are hidden in these two steps.
What happens when JavaScript being compiled? An execution context is created. When the execution context is ready, the execution step starts. All executable JavaScript codes are run line by line.
The execution context consists of a few things. To understand it, we focus on four components:
- Variable environment
- Lexical environment
- Outer
- this
In this post, the variable environment is the lead player. We will temporarily ignore other components for now.
When it comes to the execution step, the browser runs the JavaScript line by line. Meanwhile, the execution context is updated whenever a line completes running.
Variables and variable environment
Let’s start with an example.
When this piece of JavaScript is run, the first step is compiling.
During this step, the execution context is created.
Meanwhile, the variable apple
is declared with a value undefined
and stored in the variable environment.
The compiling step ends, and the execution step starts.
When the first line is executed, the apple
variable is assigned with a number 10
. The variable environment is updated at the same time.
Then the second line executes. The console starts looking for the apple
variable in its variable environment, and it finds the apple
. The console logs 10
.
The whole process ends. The entire execution context is removed.
From the examples, these are key takeaways:
- A variable assignment is actually separated into two steps.
- The compiling step takes care of the variable declaration.
- The execution step executes the assignment of the variable and the rest of the codes.
Hoisting, the tricks in a variable environment
This piece of code is slightly different from the previous one. It logs the apple
variable before declaring the variable.
We know the console will log undefined
, but let’s take a look at it from the execution context perspective step by step.
During the compiling step, line 1 is skipped because it is not related to a variable declaration. Then the apple
variable in line 2 is created in the variable environment. The compiling ends.
In the first line, the console starts looking for the apple
variable in its variable environment. At this moment, the apple
holds a value undefined
. The console logs undefined
.
Then the second line is executed, and the apple
variable is updated with a value 10
. The entire process ends.
We call this process hoisting because the variable apple
feels like being hoisted to the top. The following pseudo-codes simulate the hoisting effect.
However, from the execution context’s point of view, there is no hoisting. Nothing is hoisted. It happens because the variable is declared at the compiling step. The hosting naming is based on the result.
Hoisting and functions
Functions are slightly different because we have two options to declare a function.
An assignment happens when declaring the showName
function. By contrast, the showNumber
function is a declaration without an assignment.
Interestingly, we will see the console logs “Hey, show a number” followed by an error message “showName is not a function.”
Let’s review the two steps and see what happened.
At the compiling step, the showNumber
is a declaration, so it is actually stored at this moment.
When it comes to the showName
function, it is assigned with the undefined
because it is an assignment.
The showName
function block is not assigned until the execution step.
If you log the value of showName
instead of executing it in line 2, the console logs undefined
. An undefined
is not a function, so the error message, “showName is not a function,” displays.
One other thing worth mentioning is that the function part is not precisely accurate in my graphic. The function block is saved in a place called HEAP instead of the variable environment.
When calling a function, the browser finds the function body in the HEAP instead of the variable environment.
To keep it simple, I will put the HEAP aside and keep functions in the variable environment in graphics through the posts.
Two unusual cases to understand compiling tricks
The first case is naming conflict.
We understand that if we use the same variable names, the latter one will override the first one.
Do you know what the console will log?
The console logs the message “I’m a declaration,” though the variable is declared later. The second showNumber
variable doesn’t override the first one.
When a function and a variable have the same name, the variable declaration is ignored at the compiling step. In another word, the function has a priority.
It is a trap easily being overlooked, so we should avoid using the same variable names.
Let’s see another example.
Here, the condition in the if
is 0
, which is a negative value. The codes in the if
block never run.
If you run the codes, the console logs “undefined” instead of “apple is not defined” error message.
In this case, the apple
variable is still declared in the variable environment at the compiling step.
The if
condition only applies in the execution step, not the compiling step.
What is the takeaway?
- It takes two steps to run JavaScript: compiling and execution.
- An execution context is created at the compiling step, consisting of a variable environment and other components.
- A variable is declared at the compiling step and assigned at the execution step.
Up next…
Further reading
- If you want to know more about hoisting, take a look at the [documentation at MDN](https://developer.mozilla.org/en-US/docs/Glossary/Hoisting). A 5-minute read shares more hoisting examples.