How does javascript work?
JavaScript is a synchronous single-threaded language, which means it has a single Call Stack. Therefore it can do only one thing at a time.
The fundamental concept of JavaScript is the Execution Context. Everything in JavaScript happens inside this. Execution context can be considered as a container with 2 components:
1. Variable Environment or Memory Heap
2. Thread of Execution or Execution Stack
The variable environment stores all the variables and functions. It is a key-value pair where all declared variables and their values are stored. Also function names and their corresponding code are also stored here.
The second component called the thread of execution is the place where
the code is executed, single line at a time. JavaScript can move to the next line of code only after the execution of the first line of code. That’s synchronous single-threaded execution behavior of JavaScript.
There are two kinds of Execution Context in JavaScript:
- Global Execution Context, GEC
- Function Execution Context, FEC
GEC is the default context where all the codes which are not inside a function get executed. And FEC is created within the Global/Default execution context whenever a function is invoked.
Let’s go a bit deeper
Call Stack
The Call stack is a basic stack data structure with push and pop operations.
So when we are executing a function the execution context is pushed to the top of the stack and is popped out when we finish the execution. Each entry pushed to the stack is called a stack frame.
So initially the stack will be empty and when the engine starts, entries will be added to the stack during execution. Let’s have an example,
const firstFunction = () => {
const param = secondFunction()
console.log(param)
}const secondFunction = () => {
return 'print-me'
}firstFunction()
When an exception occurs the error message will show the stack trace of the execution, it’s basically the picture of the stack at that particular moment.
The following error will occur when I made a typo in the above code.
That’s a clear picture of the current status of the execution stack. This helps the developer in debugging the code when an exception occurs.
Memory Management
Even though the JavaScript engine handles the memory management, now or then we might get into problems like memory leakages which can be solved only if we know how the memory is managed in the JavaScript engine.
Memory lifecycle in JavaScript has 3 stages
1. Memory allocation
2. Memory use
3. Memory release
Whenever we create a variable or function the JavaScript engine allocates memory for them and will be released when it’s no longer needed.
The engine uses a Stack and Heap data structure based on whether it knows the memory requirement at compile time or not. If the memory usage is defined, the engine will use static memory allocation using stack data structure and if not uses heap data structure which is called dynamic memory allocation.
Static memory allocation is used for primitive variables and dynamic is used for objects, arrays and functions. Let’s have an example,
const posts = {
id: 1,
name: 'How JavaScript works?'
}const comments = {
comment: 'JavaScript is a wierd language',
postId: 1
}const getCommentsWithPostId = (id) => {
return comments.find(item => {
return item.postId === id
})
}let postId = 1
getCommentsWithPostId(postId)
In this example,
posts
comments
getCommentsWithPostId
will go into Head and postId
will go into the stack. But each Heap entry will have a reference in the stack.
As the contents in the Heap are not ordered in any particular way, we have to add a reference to Heap from the Stack.
The usage of memory is basically the read and write operation in the JavaScript code.
The last step of memory management is Release or popularly called Garbage Collection. Same as memory allocation the JavaScript engine handles the memory release also, through the Garbage Collector.
Identifying when to release the memory is another problem, there is no single Algorithm that immediately releases the memory when the reference becomes obsolete. Reference marking and mark and sweep algorithm are two garbage collection algorithm that gives a good approximation to this problem.
This decision making problem is the cause of memory leakage in JavaScript.
Some cyclic nature of JavaScript removes the reference in the stack but the actual value will remain in the Heap.
A good understanding of memory management in JavaScript helps to write clean code with fewer chances of memory leakages.
What is memory leakage?
It can be considered as the pieces of memory which are no longer required for the application but for some reason not released back to the operating system memory pool. An infinite loop where we are appending values to a variable is an example of that, as the garbage collector won’t run as the variables is keep on being used. Let’s have an example
let randomArray = [
for(let i = 0 ; i > 1 ; ++i) {
randomArray.push(i)
}
Here the garbage collector wont run as the array randomArray
is being used by the application, which is pushing the value of i
to the array.
Now let us look into some more memory leakage issues and see how the understanding of memory management helps us to avoid them easily.
The most common memory leakage in JavsScript is storing the values in global variables or using undeclared variables.
let a = true
b = false
if (!b) {
a = false
}
Here the variable b
becomes part of the global object and remains there even after the execution of our function.
Solution: Simple make sure this is not happening, running the code in strict code will prevent accidental globals. Simple need to add use strict;
at the beginning of JavaScript files.
Another common cause of memory leakage in JavaScript is closures. Closures are unavoidable and an integral part of JavaScript.
How do closures create memory leakage? — Generally function scoped variables will be released when the function has exited the call stack. But the closures will keep the variables alive even after the execution context and variable environment are long gone.
Prevention: Understanding the objects retained and the life span of the closure will help. Skipping closure functions with function references to external functions is recommended.
Conclusion
In this article I have tried to summarize some core concepts of JavaScript and memory management