Execution of a JavaScript program, the temporal dead zone and JS functions

Mitali Sonawane
7 min readFeb 13, 2021
Photo by Clément Hélardot on Unsplash

Note: JavaScript is synchronous and single-threaded language, which means that it executes one command at a time in a certain order.

1. Execution of a JavaScript program

Whenever a JavaScript program is run an Execution Context (EC) is created (visualize it as a container), it contains 2 components:
1. Variable Environment (Memory): consists of key-value pairs (variable: value)
2. Thread of Execution (Code): in this, the code execution takes place.

Let us walk through the execution of a simple code:

Execution Context gets created in 2 phases:
1. Memory creation phase: JavaScript skims through the program and allocates memory to all the variables and functions in the program. A variable is assigned the value ‘undefined’ and a function is assigned the block of statements written while defining that function (in case of function statements only, in case of arrow functions and function expressions the function is assigned as a value to a variable, so being a “variable” it is assigned the value ‘undefined’ during Memory creation phase).

Logical representation of Execution Context after Memory Creation Phase

2. Code Execution phase: JavaScript runs the code line-by-line.

Now as the control reaches line 1 of the program, the global execution context (GEC) gets pushed onto the call stack.

As the program encounters the command ‘var n = 3’, variable ‘n’ is assigned the value 3 after the execution of line 1 and the control moves to line 6 as the next execution command is on line 6.

In line 6, the square function is invoked. Function invocations lead to the creation of a new execution context. So a new execution context gets created inside the code section of the GEC. During the memory creation phase of the local execution context, the variables ‘num’ and ‘res’ are allotted the value ‘undefined’. As the code execution phase begins the argument ‘n’ passes the value 3 to the parameter ‘num’. The control is passed to line 3 as the function execution begins.

The command at line 3 is run and 9 is assigned to the variable ‘res’. As the control reaches line 4, the return statement looks for the value of ‘res’ in the memory section of the local execution context.

The function returns the value to variable ‘s’ as the control jumps back to line 6. After the return statement is executed, the local execution context is cleared from the code section of the GEC and the square function gets popped from the Call Stack. The return value of function gets assigned to the variable ‘s’. The control moves to line 7.

After the execution of line 7, the value of the variable ‘s’ is logged to the console. With this as the last line of code is executed, the GEC is cleared and popped from the Call Stack.

2. Let, Const and the Temporal dead zone

Let and const declarations are hoisted;
What is hoisting?
Hoisting can be understood better now as we know about the memory creation phase that takes place during the creation of execution context.
Memory is allocated to all the variables and functions even before a single line of code is executed, and the initializations if any take place during the code execution phase. The following program explains this:

In the above case, no error occurs even if the variable and function is accessed before declaration or initialization.
However, have a look at the following cases:

If the variables are hoisted then why does a ReferenceError show up?
This is because variables declared with the ‘var’ keyword are assigned the value ‘undefined’ and are attached to the global object.
The variables declared with ‘let’ or ‘const’ are assigned the value ‘undefined’ too, but they are stored in separate memory space.

Both variables ‘a’ and ‘b’ are hoisted, but variable declared with ‘let’ gets inside a separate memory space

These variables(declared with ‘let’ and ‘const’) live inside the temporal dead zone before they are initialized. As they live inside the temporal dead zone they can not be accessed before their initialization, which is why the above code gives no ReferenceError and logs 10 to the console, as ‘a’ has been accessed after the initialization command.

To avoid ‘undefined’, one may choose to declare variables using ‘const’ and ‘let’ as opposed to declaring them with ‘var’. However, the temporal dead zone may cause unexpected errors during coding, to avoid this make sure that the variable declaration and initialization part always comes at the top of your code. We can interpret this as a way of reducing the size of the temporal dead zone.

Let us also understand the difference between ReferenceError, SyntaxError and TypeError caused when we use ‘let’ and ‘const’:

1. ReferenceError: It arises when one tries to access a variable declared with ‘let’ or ‘const’ while it is still inside the temporal dead zone or when it is accessed outside that block scope.

2. SyntaxError: Let us understand with examples:
i) Duplicate declaration / re-declaration is an example of SyntaxError, while variables declared with ‘var’ would give no error in such case, using ‘let’ or ‘const’ would result in an error.

let a = 20
let a = 30
// SyntaxError: Identifier 'a' has already been declared

ii) ‘const’ variables should be declared and initialized at the same time, otherwise a SyntaxError occurs.

const a
a = 1000
console.log(a)
// SyntaxError: Missing initializer in const declaration

3. TypeError: An example of this would be re-assigning a value to a variable declared with the ‘const’ keyword. As this deals with the ‘type’ of the variable (‘const’ here) it is termed as TypeError.

const a = 10
a = 20
// TypeError: Assignment to constant variable.

3. Functions

  1. Function Statement/Function declaration:

In case of function statements/function declarations, the functions can be accessed before their declaration.

console.log(a) 
/* logs ƒ a(){
var v = 10;
console.log(v);
} */
a() // logs 10function a(){
var v = 10;
console.log(v);
}

2. Function expressions:

In function expressions a variable is initialised to a function, basically, the function acts as a value.
Function statements allow accessing of functions before the declaration, on the contrary in the below example we get a TypeError. This happens because ‘v’ here acts like any other variable, hence is assigned ‘undefined’ before the code execution phase begins. Only when the control reaches the assignment command, the function is assigned to ‘v’, calling ‘v’ after this does not cause any error.

console.log(v) // o/p: undefined v() 
// TypeError: v is not a function
var v = function () {
var a = 10;
console.log(a)
}
v() // logs 10

Hence, an important difference between a function statement and a function expression is hoisting.

3. Anonymous functions:

Functions declared without naming them are termed anonymous functions.

function(){
...
}
/* This code when run will give a SyntaxError, saying that function statements require a function name */

So what is the use of anonymous functions?
In all the cases where a function can be used as a value,
1. assigning it to a variable
2. passing it to another function
3. returning it from a function,
anonymous functions can be used.

4. Named function expressions:

Named function expressions are basically function expressions where the function which gets assigned is not an anonymous function but a named function.

var v = function func(){
console.log('called')
}
v() // logs 'called'func()
// ReferenceError: func is not defined

The ReferenceError we get in the above example is because the function ‘func’, is not defined in the outer scope. If we try to access ‘func’ in the inner scope, no error is caused, have a look:

var v = function func(){
console.log(func)
}
v()/* o/p: ƒ func(){
console.log(func)
}*/

5. First Class Functions:

We know that functions can be used as values, that is, they can be passed as arguments to other functions, can be returned from other functions, etc. Owing to this property of theirs, functions are called First Class Functions.

function f1(parameter){
return function(){
parameter()
console.log('from returned function')
}
}
var v = f1(function(){
console.log('passed as an argument')
})
v()
/* o/p: passed as an argument
from the returned function */

Thank you for reading!
These were a few concepts that I recently learnt from a tutorial that helped me a lot, really wanted to share them in my first blog of this sort.
Hope this helped!

References:
1) https://www.youtube.com/playlist?list=PLlasXeu85E9cQ32gLCvAvr9vNaUccPVNP

--

--