What is stored in the call stack
After that, we will deal with such notorious concepts as hoisting, scope, and closure.
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.
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.
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.
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.
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:
Let’s figure out what each property means and then discuss the context creation and execution steps.
In the global execution context, this holds a reference to the global object. In the browser, it’s a window object.
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).
When using an arrow function, this is not bound at all. It just inherits from the parent execution context (callee).
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.
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.
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.
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
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.
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.
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.
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.