The Javascript Runtime Environment

…and, you know, how Javascript works.

In this article we will take a look at the browser’s javascript runtime environment. We will learn how Chrome’s V8 JS Engine parses code and see how the event loop helps that code run on a single thread, both synchronously, and asynchronously, sort of. Finally, we will look at a common example that more clearly explains how this process works.

Let’s start from the beginning…

AJAX, the DOM tree, and other API’s, are not part of Javascript, they are just objects with properties and methods, provided by the browser and made available in the browser’s JS Runtime Environment.

Also in the runtime environment is a Javascript Engine that parses the code. Each browser has its own version of a JS engine. Chrome uses what it calls its V8 JS Engine and that is what we will analyze now.

The V8 JS Engine

The Javascript Runtime Environment

The Heap

The Stack

Once a function is added to the Stack the JS engine jumps right in and starts parsing its code, adding variables to the Heap, adding new function calls to the top of the stack, or sending itself to the third container where Web API calls go.

When a function returns a value, or is sent to the Web API container, it is popped off the stack and moves to the next function in the stack. If the JS Engine gets to the end of the function and no return value is explicitly written, the JS Engine returns undefined and pops off the function from the stack. This process of parsing a function and popping it off the stack is what they mean when they say Javascript runs synchronously. It does one thing at a time on a single thread.

Note - the Stack is a data structure that runs LIFO — last in first out. No function other than the one at the top of the stack will ever be in focus, and the engine will not move to the next function unless the one above it is popped off.

The Web API Container

The Callback Queue

Note - the Queue is a data structure that runs FIFO — first in first out. Whereas the Stack uses a push and pop (add to end take from end), the Queue uses push and shift (add to end take from beginning).

The Event Loop

This is what they mean when they say Javascript can run asynchronously. It isn’t actually true, it just seems true. Javascript can only ever execute one function at a time, whatever is at top of the stack, it is a synchronous language. But because the Web API container can forever add callbacks to the queue, and the queue can forever add those callbacks to the stack, we think of javascript as being asynchronous. This is really the great power of the language. Its ability to be synchronous, yet run in an asynchronous manner, like magic!

Blocking vs Non-Blocking I/O

One thing that can be a ‘blocking i/o’ is an HTTP request. Say you make a request to some external site data and you have to wait for that site’s network. The network may never respond and your code will ultimately be stuck. But Javascript handles this in the runtime environment. It sends the HTTP request to the Web API and pops it off the stack so that the next function can run while the Web API waits for its data to return. If the HTTP request never gets its data back the rest of the program will continue running. This is what we mean when we say JS is a Non-Blocking language.

A Typical Example

setTimeout(function(){
console.log('Hey, why am I last?');
}, 0);
function sayHi(){
console.log('Hello');
}
function sayBye(){
console.log('Goodbye');
}
sayHi();
sayBye();

If you copy and paste this code into your console you will see it prints ‘Hello’, then ‘Goodbye’, then undefined, then ‘Hey, why am I last?’. Even though the setTimeout function is called first and is supposed to run in zero seconds, it outputs last. Go through each line and try to understand the process of the JS Engine as it parses this code. Try and think why the setTimeout() call prints to the console after sayHi() and sayBye().

When you’re done thinking it through, let’s take a look at the exact way in which the V8 JS Engine handles this code…

1. The JS Engine parses the entire script checking for syntax errors. It sees none so it starts more fully parsing from the top.

2. It sees a setTimeout function call and pushes it to the top of the Stack.

3. It jumps right into the function call, sees it is part of the Web API and thus sends it over to the Web API container and pops it off the Stack.

4. Because the timer is set to 0 seconds, the Web API container immediately pushes its anonymous function to the callback queue. The event loop checks the Stack to see if it’s empty, but it is not, because…

5–6. …as soon as the setTimeout function was sent over to the Web API container, the V8 JS Engine saw two function declarations, stored them in the Heap and then saw a function call of sayHi() and pushed it to the Stack.

7. That sayHi() function calls console.log(), which is pushed to the top of the stack.

8. The JS Engine jumps right into parsing that function. It returns a message to the console - ‘Hi’ - and is popped off.

9. The JS Engine moves back into the sayHi() function, gets to its closing bracket and pops it off the Stack.

10–12. As soon it’s popped off the Stack the next function call, sayBye(), is pushed to the Stack. It is parsed, calls console.log(), which gets pushed to the top of the Stack, it returns a message, is popped off the stack, then its original function is popped off the Stack.

13. The Event Loop sees that the Stack is finally empty. It lets the Queue know and the Queue pushes its anonymous function to the Stack.

14. The anonymous function is parsed and it calls console.log(), which is pushed on the stack.

15. The console.log() function is executed and is popped off the Stack.

16. The anonymous function is popped off the Stack and the program is finally finished.

PS - If you copy and pasted the code to the console you may have noticed an ‘undefined’ result. This is because none of the main functions are returning a value. Instead, they are calling console.log(), which is being executed by the parser and getting popped off, then the parser is getting to the end of the main function without seeing a returned result, so it returns ‘undefined’, then it pops the function off the stack.

Now You Try!

Browser Environment vs Node.js Environment

Other Considerations

More Resources!

Event Loop & Concurrency - Explained by Mozilla, using code examples
Another similar explanation - by Aseem Raj Baranwal
Lazy Parsing & Full Parsing - Is it worth it?
A more detailed explanation- More literal explanation

developer. entrepreneur. student.