Every time you start a JavaScript project probably you follow most of these common steps:

  • Setup you the project with a package manager like npm or Yarn
  • Add a bundler that could be webpack, rollup.js, or any other. As alternative you can use a more aged task runner like Grunt or Gulp, or even a mix of them.
  • Develop your source code
  • build your project (this simply means generate the final JS code)
  • and finally... see the result on a web page

But…

There is another important step that is missing and happens behind-the-scenes...

have you ever wonder what happens inside the browser since when the JavaScript file is received to its execution?

Javascript Engine

The first and most important character in this story is the JavaScript Engine.

But, what is it?

JavaScript Engine is the program that take the JavaScript code and execute it.

Note

Saying “execute it” it’s not exactly what happens. We usually think at JavaScript as an interpreted language rather than a compiled language. That was true in the past but nowadays things are more complicated and deserve a deeper explanation. For now we can say that:

Javascript Engine compiles the code line-by-line just before it is being executed and we call it just-in-time compilation

Now, if you want to understand JS engine behaviour you need to start thinking like a JavaScript Engine.

Suppose that you web page contains only this simple script:

const jedi = "Luke";
let jediList = ["Luke", "yoda", "Obi-Wan"];
const defaultJediList = ["Luke", "yoda", "Obi-Wan"];
const message = "Use the force";

function useTheForce(character) {
  const otherJediList = ["Mace Windu", "Kit Fisto"];
  let jediList = [ ...defaultJediList, ...otherJediList ];

  if (jediList.includes(character)) {
    console.log("%s %s !", message, character);
    // => "Use the force Luke!"
  }
}

Execution context

If you were a JS Engine the first thing you do is to prepare a comfortable environment to host the incoming code. This environment is named execution Context (EC) and we can simply define it as

the environment where JavaScript code is executed

To make this concept more pragmatic we can say that it contains all the information necessary for code execution, this includes the this reference, and all the variables, objects and functions definitions).

Now, what happens at page loading is that a default EC is created with the name of Global execution context.

It contains all the the built-in variables and functions of the language like the properties Math, String, console , or window (that is a reference itself to the Global Object).

Back to our code, and remember to think like a JS Engine. How would you read the above script?

As we already know, the answer is line by line, top to bottom.

**Line 1** - Engine: I see a constant declaration, I store it

**Line 2 **- Engine: I see a variable declaration, I store it

**Line 3 **- Engine: I see a constant declaration, I store it

**Line 4 **- Engine: I see a variable declaration, I store it

**Line 6 **- Engine: I see a function declaration, I store it

At the end what happened is that some declarations are stored in the global memory, no code execution happens. Anyway, there is a new character in our story I would like to introduce…

Lexical Environment

When we speak about storing variable and function declarations I mean saving somewhere the association of identifiers to their values. This place is a component called Lexical Environment and it’s managed following the lexical nesting structure of the code.

More or less the behaviour is this:

  • every time a scope is found a new Lexical Environment is created
  • for each Lexical Environment there is an Environment Record that maintains the list of identifier bindings found in the current scope
  • An outer environment reference maintains a link to the external Lexical Environment (or scope).

In our code we have only the global scope, its lexical environment contains the four variable and the function declaration.

Note that for the outer environment reference is null as it doesn’t have any parent scope.

JavaScript Execution Context JSMonday

Call stack

Our script is interesting: very clean and dry. Now I gonna make the things more complicated by adding a function call (Oh my God, now it’s very tricky! 😱😱)

const jedi = "Luke";
let jediList = ["Luke", "yoda", "Obi-Wan"];
const defaultJediList = ["Luke", "yoda", "Obi-Wan"];
const message = "Use the force";

function useTheForce(character) {
  const otherJediList = ["Mace Windu", "Kit Fisto"];
  let jediList = [ ...defaultJediList, ...otherJediList ];

  if (jediList.includes(character)) {
    console.log("%s %s !", message, character);
    // => "Use the force Luke!"
  }
}

useTheForce(jedi);

What happens when you read the last line? (remember you are the Engine)

The function call cause the creation of a brand new EC, a local execution context. Remember that JavaScript is single thread so we can only execute one thing at time: the global EC will be frozen and execution pass to the new one. To manage the code execution among different contexts we need a data structure with the list of all existing ones.

This structure is the call stack, as the name suggests, it’s a simple LIFO stack (Last In First Out).

JavaScript Execution Context JSMonday

Think about it as a layer cake: the base layer is the Global execution context (inserted by default in this structure), every time there is a function call you need to add a new layer on top of the existing one (Local execution context), when function execution is completed you need to “eat” the layer of cake on top to go on with the code execution on the layer (or EC) below. That means delete the EC on the top and free memory used for its data structures.

Local execution context

What we know now is that

Every time a function call happens the Engine creates a new execution context

This action includes two phase:

  • creation
  • execution

creation

In this phase JS engine perform the following action:

  • create the Lexical Environment. Like global EC it contains all the variables, and function declarations defined inside the function but it also the formal parameters and the arguments object.
  • create the Variable Environment. This is basically a copy of the Lexical Environment with some differences. For the scope of this article you should only know that it exists.
  • determines the value of this

Note that Lexical Environment is created every time a new scope (like if, for statement etc… are encountered).

execution

This is very straightforward and means only execute the code.

Now we need slow down a little and explain better how variable values are provided.

Scope chain

Let’s start with a definition:

Scope chain is the stack of lexical environments active for the current code. The sequence is defined by the outer environment reference that connect each element with its parent.

The scope chain is very important because is the key for identifier resolution and we can define it as:

the process that returns the value of a variable by looking up the scope chain.

Everything start from the most immediate lexical environment looking for a match and if it’s not found we need to proceed to the next one in the chain til the global scope. If no match is found, a ReferenceError is thrown.

Considers the piece of our code inside the if statement:

...
if (jediList.includes(character)) {
  console.log("%s %s !", message, character);
  // => "Use the force Luke!"
}
...

It generates a scope and therefore a brand new Lexical environment is chained.

In this two lines of code we have the two variable character and message. For both the identifier resolution start from the IF Lexical Environment, as the environment record doesn’t contains any item the search moves to the parent where a match is found for character and so the value “‘Luke’” is associated. For the message identifier we need to make another step and move to the Global Lexical Environment where we have a match.

JavaScript Execution Context JSMonday

Conclusion

Now you can ask yourself if this article is useful, to be honest I can say that you can ignore at all what you have learned in the last five minutes, probably your code will be good enough and everybody will be happy of your work. But if you want to understand JavaScript deeply this is the right direction to follow.

Think about a car: you can drive it without any knowledge about how the instructions you give to the steering wheel, accelerator or brake will be transformed into movements of the vehicle. But if you wish to get the best performance you need to open the hood, take al look at the engine and understand how gears work together.

Now you know

  • what is it the Javascript Engine and the Execution Context
  • how and when an execution context is created
  • how declarations are managed
  • how variable values are resolved thanks to scope chain

That’s all folks!

Did you like this article? Consider becoming a Patron!

This article is CC0 1.0 (Public Domain) licensed.