JavaScript execution context — from compiling to execution (part 1)

Carson
7 min readFeb 3, 2020

--

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

--

--