JavaScript Under the hood

JavaScript, the language of the web, breathes life into interactive interfaces and dynamic applications. While writing JavaScript code feels intuitive, its execution involves a fascinating interplay between the browser and the engine under the hood.

TL;DR

  • JavaScript is a programming language that makes web pages interactive.

  • When you write JavaScript code, your computer translates it line by line into instructions the browser understands.

  • These instructions tell the browser what to do, like showing text, changing colours, or reacting to clicks.

The Heart of JavaScript: The Engine

The execution starts with the JavaScript engine, a program embedded within the browser (like Chrome's V8 or Firefox's SpiderMonkey) or an environment like Node JS. This JS engine is responsible for interpreting and executing JavaScript code.

From Text to Machine: Parsing and Transformation

When you write JavaScript code, it's just text. The engine transforms this text into a format it can comprehend. This process involves:

  • Lexical analysis: Breaking the code into tokens (keywords, identifiers, operators, etc.).

  • Syntax analysis: Verifying the code adheres to JavaScript's grammar rules.

  • Abstract Syntax Tree (AST) generation: Creating a tree-like structure representing the code's overall structure(parsing). The JS engine utilizes an AST as an intermediate representation after parsing the code. This allows for various optimizations and analyses before execution.

Popular JavaScript Engines

  • V8 (Google Chrome): An open-source, high-performance engine known for its JIT compilation and speed.

  • SpiderMonkey (Mozilla Firefox): Another open-source engine with a long history, used in Firefox and other products.

  • JavaScriptCore (Safari): WebKit's JavaScript engine used in Apple's Safari browser and other WebKit-based browsers.

  • Chakra (Microsoft Edge): The JavaScript engine in Microsoft Edge, known for its tight integration with the .NET platform.

Understanding the Code: Just-in-Time (JIT) Compilation

JavaScript engines traditionally employed interpretation, executing the code line by line. Modern engines leverage the JIT compilation for better performance. Here's the essence:

  1. Initial Interpretation: The engine starts by interpreting the code, and translating it into bytecode (a low-level instruction set).

  2. Execution and Monitoring: As the code runs, the engine gathers information about how variables are used and function calls are made.

  3. Optimization: Based on this data, the engine identifies frequently executed parts of the code. These sections are then compiled into machine code, the language directly understood by the computer's processor, for faster execution.

This approach offers a balance between flexibility (interpretation) and speed (machine code).

Single-Threaded: A Core Concept

JavaScript is inherently single-threaded. This means it can only execute one task at a time. While this might seem limiting, JavaScript employs an ingenious technique to handle asynchronous operations.

Imagine a single chef in a kitchen. This chef can only prepare one dish at a time. While one dish is being cooked, other orders have to wait. This analogy perfectly illustrates the essence of single-threaded execution in JavaScript.

JavaScript, unlike some other programming languages, has just one call stack. The call stack acts like a stack of plates in a restaurant. Each plate represents a function being executed. The engine focuses on completing the function on top of the stack (the one currently being processed) before moving on to the next one (pushing it down the stack).

Here's a breakdown of how this single-threaded nature plays out:

  1. Synchronous Execution: When you write code like console.log("Hello"), the engine adds this function call (printing "Hello") to the top of the call stack and executes it. Only after "Hello" is printed can the engine move on to the next line of code.

  2. Blocking Operations: If you encounter a long-running task, like fetching data from a server, the entire browser would freeze in a single-threaded world. The reason? The function responsible for fetching data would be stuck at the top of the call stack, preventing the engine from processing any other code until the data arrives.

This inherent limitation of single-threading could lead to a poor user experience if the browser becomes unresponsive during long-running tasks.

Async to the Rescue

Imagine you have a function that takes 5 seconds to complete. In a single-threaded environment, the entire browser would freeze for those 5 seconds. To prevent this, JavaScript utilizes an event loop and callbacks:

  1. Event Queue: When a long-running task (like fetching data from a server) is encountered, it's placed in an event queue.

  2. Call Stack: Think of a stack of plates in a restaurant. The call stack keeps track of the currently executing functions. The engine focuses on completing the function on top of the stack before moving on to the next one.

  3. Event Loop & Concurrency Model: JavaScript's single-threaded nature is balanced with an event loop and a sophisticated concurrency model to handle asynchronous operations effectively. The event loop constantly monitors the call stack and event queue, ensuring smooth task execution even when dealing with long-running operations.

  4. Callback Execution: If a task is present in the queue, the event loop retrieves it and pushes it onto the call stack for execution. Once the task finishes, it's removed from the call stack.

  5. Execution Context: Each function creates its execution context, an environment that holds information about the function's variables, arguments, and the code itself. This isolation mechanism prevents variables from accidentally interfering with each other in different parts of the code.

This mechanism allows the browser to remain responsive while long-running tasks are being processed in the background. Callbacks are functions passed to other functions, designed to be executed once the long-running task is complete.

Microtasks: There's another layer of asynchronous behaviour with microtasks. These are tiny tasks the JavaScript engine schedules, often used for internal housekeeping or updating the UI. Microtasks have higher priority than callbacks and are processed after the current function on the call stack finishes but before the event queue is checked.

Memory Management:

  • Memory Heap: JavaScript utilizes a memory heap to store dynamically allocated data structures like objects and arrays. This heap grows and shrinks as needed during program execution.

  • Garbage Collection: To prevent memory leaks, JavaScript employs garbage collection. This automatic process identifies unused objects in the heap and deallocates the memory they occupy, ensuring efficient memory usage.

Explain like I'm 5 years old

Imagine you're giving instructions to your friend on how to build a Lego castle. That's kind of like writing JavaScript code! You tell your friend (the computer) exactly what pieces to use and how to put them together.

Here's what happens when you run JavaScript code:

  1. Writing the Code: You write the instructions (code) using a program like Notepad or a fancy code editor like a playground. It's like writing the steps for your Lego castle.

  2. Calling the Interpreter: When you tell the computer to run your code, it's like asking your friend to start building the castle. The computer uses a special program called an interpreter to read and understand your instructions.

  3. Understanding the Code: The interpreter goes through your code line by line, like your friend following each step of your instructions. It figures out what each piece of code means and what the computer needs to do.

  4. Executing the Code: Once the interpreter understands a line of code, it tells the computer to do something. This might be showing something on the screen, making a calculation, or remembering some information. It's like your friend following your instructions to put the Lego bricks together.

  5. Seeing the Output: Finally, after the computer finishes following all your instructions, you see the result (output) on the screen. It's like seeing your finished Lego castle!

Remember: JavaScript code is like a set of instructions for the computer. The interpreter helps the computer understand those instructions and shows you the result!

0
Subscribe to my newsletter

Read articles from Nicanor Talks Web directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nicanor Talks Web
Nicanor Talks Web

Hi there! I'm Nicanor, and I'm a Software Engineer. I'm passionate about solving humanitarian problems through tech, and I love sharing my knowledge and insights with others.