Call Stack in Javascript
A call stack is a system that allows an interpreter (such as the JavaScript interpreter in a web browser) to maintain track of its position in a script that calls numerous functions — for example, what function is presently being executed and what functions are called from inside that function, and so on.
When a script invokes a function, the interpreter adds it to the call stack and then executes it. Any functions called by that function are added to the call stack and executed where their calls are reached. When the current function is completed, the interpreter removes it from the stack and continues execution where it left off in the previous code listing.
The Java engine uses a call stack to keep track of its position in code that invokes numerous methods. It knows what function is presently being executed and what functions are triggered from within that function…
In addition, the JavaScript Engine manages execution contexts via a call stack:
- The execution context is global.
- The contexts for function execution
The call stack operates on the last-in-first-out (LIFO) concept. When you run a script, the JavaScript engine builds a global execution context on top of the call stack. When a function is called, the JavaScript engine constructs a function execution context for it, moves it to the top of the call stack, and begins executing it.
The JavaScript engine puts the called function at the top of the call stack and establishes a new function execution context for it when a function calls another function. The JavaScript engine removes the current function of the call stack after it completes and continues the execution from that point on. When the call stack is cleared, the script will terminate.
Example:
function adding_string(s1, s2) {
return s1 + s2;
}
function change_string(S_arg1, S_arg2) {
return `${adding_string(S_arg1, S_arg2)} for learning technology.`;
}
let final_string = change_string("Enablegeek ", "is awesome");
console.log(final_string);
Output:
Enablegeek is awesome for learning technology.
Step 1: The main() or global() function denotes the global execution context, which is where the call stack is placed when the JavaScript engine performs this script.
The creation step of the global execution context is followed by the execution phase.
Step 2: The change_string(“Enablegeek “, “is awesome”) function call is carried out by the JavaScript engine, which also establishes a function execution context and pushes the change_string() function to the top of the call stack:
Since the change_string() function is at the top of the call stack, the JavaScript engine begins executing it.
Step 3: adding_string() is called by the change_string() method. The adding_string() method is now given a new function execution context by the JavaScript engine, which also moves it to the top of the call stack:
Step 4: When all of the functions on the call stack have been pushed, the JavaScript engine executes the adding_string() function and pops it from the call stack:
Step 5: The change_string() function is now at the top of the call stack, and the JavaScript engine performs it and pops it from the call stack.
Step 6: Because the call stack is now empty, the script terminates execution:
The diagram below shows the final state of the call stack at all stages:
Stack Overflow
The call stack has a defined size depending on whether the host environment is a web browser or Node.js. A stack overflow error will occur if the number of execution contexts exceeds the size of the stack.
For example, when you run a recursive function named stack_overflow() with no exit condition, the JavaScript engine will throw a stack overflow error:
Example:
function stack_overflow() {
stack_overflow();
}
stack_overflow();
Output:
/Desktop/Javascript/first.js:2
stack_overflow();
^
RangeError: Maximum call stack size exceeded
at stack_overflow (/Desktop/Javascript/first.js:2:3)
Memory Management
Low-level memory management primitives such as malloc(), realloc(), calloc(), and free() are available in languages such as C. The developer uses these primitives to explicitly allocate and free memory from and to the operating system.
Simultaneously, JavaScript allocates memory when objects, arrays, strings, etc. are created and “automatically” frees them when they are no longer used, a process known as garbage collection. This supposedly “automatic” nature of freeing up resources causes confusion and gives JavaScript (and other high-level-language) developers the erroneous idea that they may ignore memory management. This is a terrible decision.
The Life Cycle of Memory
The memory life cycle is essentially the same regardless of the programming language:
- Make sure you have enough memory and allocate it.
- Utilize the available memory (read, write).
- When the allocated part of RAM (memory) is no longer required, it should be released.
The second section is expressly stated in all languages. The first and last sections are clear in low-level languages but generally implicit in high-level languages such as JavaScript.
Allocate Memory: The operating system allocates memory for your software to use. In low-level languages, this is an explicit operation that you should manage as a developer. High-level languages, on the other hand, take care of this for you.
Use Memory: This is the point at which your software uses the previously allocated memory. As you use the allocated variables in your code, read and write operations occur.
Release Memory: Now is the moment to free up all of the memory that you no longer require so that it can become free and available again. This one, like the Allocate memory operation, is explicit in low-level languages.
JavaScript Memory Allocation
Now we’ll go through how JavaScript’s first step (allocating memory) works. JavaScript relieves developers of the task of managing memory allocations; JavaScript does it on its own, in addition to declaring variables.
Example:
const Num = 111127;
const Str = "September";
const Obj = {
name: 'Tuhin',
age: 22,
};
const Arr = [127356, 239878, 209092];
function func(a) {
return a + 2;
}
In the above example, 111127 is assigned to the variable Num, which is allocated in the memory for numbers. Then the Str variable allocates a memory location for storing string-typed values. In this case, it’s “September”.
Similarly, the object and array and a callable object (javascript function) allocate their memory location for execution.
JavaScript Memory Releases
The majority of memory management challenges arise at this point. The most difficult aspect here is determining when the allotted memory is no longer required. It is frequently necessary for the developer to establish where in the software such a chunk of memory is no longer required and free it.
High-level languages include a piece of software known as a garbage collector, whose function is to watch memory allocation and use it in order to determine when a chunk of allocated memory is no longer needed, at which point it will automatically free it.
Unfortunately, this is an approximation because the overall problem of determining if some piece of memory is required is undecidable (cannot be solved by an algorithm).
Most garbage collectors collect memory that can no longer be accessed, such as after all variables pointing to it have gone out of scope. That is, however, an underestimation of the number of memory spaces that can be gathered, because a memory location may still have a variable referring to it in scope at any point, but it will never be accessed again.
Garbage Collection
Garbage collectors implement a restriction of a solution to the broader problem since determining if some memory is “not needed anymore” is undecidable. This section will describe the fundamental concepts required to comprehend the primary garbage collection algorithms and their constraints.
There are two algorithms used in JavaScript for garbage collection.
- Reference-counting garbage collection,
- Mark-and-sweep algorithm
Memory Leaks
We now know that the engine allocates RAM for everything we define in JavaScript and releases it when we no longer require it. The second thing that came to me was, “Where would this be stored?” JavaScript engines can store data in two places: the memory heap and the stack. Heaps and stacks are two types of data structures that the engine utilizes for various purposes.
Different programming languages promote different methods of memory management. However, whether or not a certain portion of memory is used is an unsolvable problem. In other words, only developers can determine whether or not a portion of memory can be returned to the operating system.
Certain programming languages include capabilities that assist developers in accomplishing this. Others demand developers be entirely transparent about when memory is being used. There are nice articles on manual and automatic memory management on Wikipedia.