What is stored in the call stack

Photo by Iva Rajović on Unsplash

Introduction

In this post, we’ll take look at one of the most important and fundamental parts of JavaScript, the execution context. We will define the structure of a context, its lifecycle, and how the execution stack (call stack) is formed.
After that, we will deal with such notorious concepts as hoisting, scope, and closure.

Execution Context

There are three types of ECMAScript code: global code, function code, and eval code (which is not covered in this article). Every code is evaluated in its own execution context.

Execution context is a concept that describes the environment in which code is executed. In simple words, it’s just a set of objects that are created and used by the JavaScript engine at runtime.

Before the JavaScript engine starts executing your script files, the global execution context is created. Every line of code that is not a part of a function body is a global code. Such code is executed inside the global context, which can be only one per program.

During the execution of the global code, the JavaScript engine may reach a function invocation instruction. When that happens, a function execution context is created and a function code is executed.

Hence it can be said that one execution context can create another execution context, i.e. a function calls another function (or the global context calls a function), and so on. Together these created contexts form the execution stack.

Execution Stack

The execution stack, which is also known as the call stack, is a last-in-first-out (LIFO) data structure. It’s created and managed by the JavaScript engine for storing execution contexts at a runtime.

A context which creates (calls) another context is called a caller. A context which is being created is called a callee.

When a caller creates a callee, the caller suspends its execution and passes the control flow to the callee. The callee is pushed onto the stack and becomes a running (active) execution context. After the callee’s code is executed entirely, it returns control to the caller, and the evaluation of the caller’s context proceeds (it may activate other contexts) till its end, and so on.

Execution stack

In the example above, the global execution context is created and put onto the stack. Then on each function invocation, the function execution context is created and put onto the stack. After the function is completely executed, its context is removed from the stack.

JavaScript is a single-threaded language. The engine always executes the function that’s on the top of the execution stack.

We know that in JavaScript there are two main execution context types — global and function. They are created by the JavaScript engine and stored in the execution stack (call stack). Their goal is to describe the environment in which code is executed.

You might be wondering what, exactly, environment means.

Execution Context Structure

First of all, we need to define the structure of the execution context. It can be represented as an object with three properties:

Execution context structure

Let’s figure out what each property means and then discuss the context creation and execution steps.

This binding

In the global execution context, this holds a reference to the global object. In the browser, it’s a window object.

ThisBinding in global execution context

In the function execution context, the value of this depends on how the function is called. If it’s called as a method of an object, the value of this is set to that object. Otherwise, the value of this is set to the global object or undefined(in strict mode).

ThisBinding in function execution context

When using an arrow function, this is not bound at all. It just inherits from the parent execution context (callee).

ThisBinding in Arrow Functions

Lexical environment

The Lexical Environment consists of two entries:

  • Environment Record — a structure that maps identifiers to their values within the scope of its associated Lexical Environment. Such records store values of identifiers declared withlet or const keywords.
  • Outer reference — holds a reference to the parent Lexical Environment. It means that the JavaScript engine can look for variables inside the outer environment if they are not found in the current Lexical Environment.

In the global execution context, outer reference is set to null. In the Environment Record, embedded language entities are available (such as object, array, and so on) as well as global variables, which you define.

Global Execution Context (Lexical Environment)

In the function execution context, outer reference is set to the parent Lexical Environment. It could be a global or function context, depending on where the function is called. Variables declared by the user inside the function are stored in its Environment Record as well as in arguments array-like object.

Function Execution Context (Lexical Environment)

Variable environment

ECMA-262 specification sais:

The LexicalEnvironment and VariableEnvironment components of an execution context are always Lexical Environments. Variable Environment identifies the Lexical Environment whose EnvironmentRecord holds bindings created by VariableStatements within this execution context.

In other words, Variable Environment stores identifier-value mappings declared with the var keyword within its execution context.

Context Creation and Execution

In JavaScript, each execution context has two separate phases: a creation phase and an execution phase.

During the creation phase, window and this are created (if we’re in the global context), variable declarations in Environment Record are assigned a default value of undefined (or uninitialized), and every function declaration is placed entirely into memory.

Once we enter the execution phase, the JavaScript engine starts executing the code line by line and assigns the real values to the variables already living in memory.

Execution context phases

In the creation phase, variables declared with let and const keywords are assigned a default value of uninzialized. That’s why, when you try to access such variables, you will get ReferenceError.

Reference error

Scope, Hoisting, and Closures

Knowing the structure of the context and its lifecycle we can easily understand the meaning of hoisting, scope, and closures.

Hoisting is just the process of assigning variable declarations a default value and placing function declarations into memory during the creation phase. Nothing actually is moved in your code.

Closure is a way to save the Lexical Environment of a function in the memory after its execution context is removed from the stack. A closure gives you access to an outer function’s Lexical Environment from an inner function. Just define a function inside another function and return it or pass it to another function.

JavaScript closure

Scope is just another way of talking about Lexical Environment. It’s like a boundary that defines what variables can be accessed in the current execution context.

Conclusion

Now you should have a better understanding of what is happening when the JavaScript engine executes code, what data is stored in execution context and execution stack, how variables and functions are hoisted, and how their values are determined.

Such in-depth knowledge is not necessary to write JavaScript code. But it helps to understand concepts like hoisting and closure, which are commonly used in development.

References

http://www.ecma-international.org/ecma-262/10.0/index.html


JavaScript Internals: Execution Context was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.