How does JavaScript really work? (Part 2)

Home Dev How does JavaScript really work? (Part 2)

How memory management, call stack, threading, and the event loop works with JavaScript’s V8 engine.

Go to the profile of Priyesh Patel
Photo by Samuel Zeller on Unsplash

In Part 1 of this article, I gave a brief overview of how programming languages work in general and discussed the V8 engine’s pipeline in detail. This post will cover a few more important concepts that every JavaScript programmer must know and are not tied only to the V8 engine.

There are 2 main concerns for any programmer: Time complexity and Space complexity. Part 1 covers the speed and optimization part by V8 for the improved execution time of JavaScript, this part will focus on the memory management aspects.

Tip: Turn reusable JS code into shared components with Bit (GitHub). Bit helps everyone build modular JavaScript applications, easily share components between projects and team, and build better & faster.

The memory heap

Orinoco logo: V8’s garbage collector

Mark and Sweep algorithm

Mark and Sweep algorithm

In order to determine the objects that can be safely deleted from the memory heap, this simple and effective algorithm is used. The algorithm’s name itself describes its working; mark the objects as reachable/unreachable and sweep the unreachable ones.

The garbage collector starts with the root or global objects periodically and moves to the objects referenced by them, and then to the objects referenced by these references and so on. All the unreachable objects are then cleared.

Memory leaks

Although the garbage collection is effective, the developers should not be under the impression that they need not worry about memory management. Managing memory is a complex procedure and which piece of memory is not required cannot be decided by an algorithm.

Memory leaks are parts of memory that the application needed and used in the past and it is not needed anymore but its storage is yet not returned to the memory pool.

Following are some of the common mistakes that cause this memory leaks in your application.

Global variables: If you keep creating global variables, they will stick around throughout the execution of the program even if they are not needed. If these variables are deeply nested objects, a lot of memory is wasted.

var a = { ... }
var b = { ... }
function hello() {
c = a; // this is the global variable that you aren't aware of.
}

If you try to access a variable which was not declared previously, you will create a variable in the global scope. In the above example, ‘c’ is that variable/object that you didn’t implicitly create using the ‘var’ keyword.

Event Listeners: This may happen when you create a lot of event listeners to make your website interactive or maybe just for those flashy animations and forget to remove them when the user moves to some other page in your single page application. Now when the user moves back and forth between these pages, these listeners keep adding up.

var element = document.getElementById('button');
element.addEventListener('click', onClick)

Intervals and Timeouts: When referencing objects inside these closures, the garbage collector will never clear the objects until the closure themselves are cleared.

setInterval(() => {
// reference objects
}
// now forget to clear the interval.
// you just created a memory leak!

Removed DOM elements: This one is similar to the global variable memory leak and very common. DOM elements exist in the Object Graph memory and the DOM tree. This scenario is better explained by an example.

var terminator = document.getElementById('terminate');
var badElem = document.getElementById('toDelete');
terminator.addEventListener('click', function() {memory
badElem.remove();
});

After you click the button with id = ‘terminate’ , toDelete is removed from the DOM. But since it’s still referenced within the listener, the allocated memory for the object is still used.

var terminator = document.getElementById('terminate');
terminator.addEventListener('click', function() {
var badElem = document.getElementById('toDelete');
badElem.remove();
});

Now, the badElem variable is a local variable and when the remove operation completes, the memory can be reclaimed by the garbage collector.

The Call stack

A stack is a data structure that follows the LIFO(Last in first out) approach to store and access the data. In case of the JavaScript engine, the stack is used to remember the location of last executed command in a function.

function multiplyByTwo(x) {
return x*2;
}
function calculate() {
const sum = 4 + 2;
return multiplyByTwo(sum);
}
calculate()
var hello = "some more code follows"
  1. The engine gets to know that we have two functions in our program.
  2. Run the calculate() function.
  3. Push calculate on the call stack and calculate the sum.
  4. Run multiplyByTwo() function.
  5. Push the multiplyByTwo function on the call stack and perform the arithmetic operation x*2.
  6. While returning with the value, pop multiplyByTwo() from the stack and go back to calculate() function.
  7. While returning from the calculate() function, pop calculate from the stack and continue further execution of the code.

Stack Overflow

The amount of consecutive push you can do without doing a pop on the stack depends on the size of the stack. If you run the limit and keep pushing, it will lead to something called stack overflow and chrome will throw an error along with the snapshot of the stack also called as the stack frame.

Recursion: When a function calls itself, it is called recursion. This concept is very useful in some cases where you want to reduce the time an algorithm takes for execution(time complexity), but complex to understand and implement.

In the following example, the base case is never executed and the lonely function keeps calling itself without returning eventually causing a stack overflow.

function lonely() {
if (false) {
return 1; // the base case
}
lonely(); // the recursive call
}

Why is JavaScript single threaded?

A thread represents how many parts of programs can you execute independently and at the same time. The easiest way to find whether a language is single threaded or multithreaded is by knowing how many call stacks it has. JS has one, so it is a single threaded language.

So isn’t this a bottleneck? If I run multiple time consuming also referred to as blocking operations like HTTP requests, the program will have to wait for the response of each operation before executing the next one.

To get around this problem, we need a way to do tasks asynchronously even though we have a single thread. This is where the event loop comes handy.

The Event Loop

Until now, most of the things that we talked about were included in the V8, but if you search the V8 code base for implementations of things like setTimeout or DOM, they are not present in the V8. Other than the runtime engine, JS consists of something called as Web APIs which are provided by the browser to extend JS.

I don’t think I can explain this concept as efficiently as Philips Roberts does in this video.

Conclusion

There is still a lot that goes in making a programming language and the implementations keep changing over the years. I hope that these two blogs have helped you in becoming a better JS programmer and embrace the weird parts of JS. You should be now comfortable with jargons like ‘V8’, ‘event loop’, ‘call stack’, etc.

Most of the students (like me) start with a new framework before learning vanilla JS. Now they should be comfortable with what happens under the hood which in turn will help them to write better code.

Leave a Reply

Your email address will not be published.