Our Blog:

How JavaScript Works Behind the Scenes


Senior
Andrej Vajagic

Andrej Vajagic

19.07.2023, 13:21

Read time: undefined mins

How JavaScript Works Behind the Scenes

JavaScript, the popular coding language for websites, might seem like magic when you see it in action. But, if you look closer, you'll find interesting steps and tools that make it work. In this blog, we're going to explore important parts of JavaScript like the event loop, execution context, and how it handles memory. We'll help you understand what's happening behind the scenes when you use JavaScript, so you can improve your coding skills.

Engine

Every web browser and JavaScript runtime has something called a JavaScript engine at its heart. This engine, which is a complex software, translates and runs your code. One of the best-known JavaScript engines is V8 (JavaScript engine), which is open-source and made by Google. It's known for being fast and efficient. You can find it inside popular browsers like Google Chrome and tools like Node.js, where it helps to run JavaScript code.

Compilation vs Interpretation

Before we start explaining how engine works behind the scene let us first explain Compilation vs Interpretation.

Compilation: Source code is translated into machine code or bytecode before execution. Optimized executable files are generated, resulting in faster execution. However, it may have limited portability and longer development cycles. Compilation Interpretation: Source code is executed line-by-line or statement-by-statement at runtime. More portable, simpler debugging, but slower performance due to the interpretation overhead. Interpretation Just-In-Time (JIT) Compilation: Hybrid approach that combines elements of both compilation and interpretation. Translates bytecode into machine code at runtime, offering a balance of performance and portability, with a potential initial startup overhead. Modern JavaScript uses this approach. Just-In-Time (JIT) Compilation

In case of JavaScript it work like this:

  • Our code enters the engine → Engine PARSES (reads) the code → Code is parsed to AST(Abstract syntax tree)
  • ASTCOMPILATION (Just in time compilation) → MACHINE CODE
  • MACHINE CODE (unoptimized version) → EXECUTION right away (execution happens in Call Stack)
  • MACHINE CODEEXECUTION as fast as posible (execution happens in Call Stack)
  • Optimization (In background) → RE-COMPILEEXECUTION (and if needed optimization again)

When code is compiled it can be executed in JS Engine.

Runtime in browser

Runtime

  • Main part of any JavaScript runtime is JavaScript Engine. But Engine can't work alone.
  • Web APIs (For browser): Web APIs are interfaces provided by web browsers to enable interaction between web applications (such as websites) and the web browser itself. They are not part of JavaScript language. JavaScript gets access to these APIs trough global window object:
    • DOM (Document Object Model) API
    • Fetch API
    • Web Storage API
    • WebRTC (Real-Time Communication) API
    • And much more
  • Callback queue is a fundamental component of JavaScript's event loop mechanism. It plays a crucial role in managing asynchronous code execution in the browser environment.
    • Click event handler happen → Callback function is put in Callback queue
    • Execution context is empty → Callback function is moved to Call stack and executed →
    • Moving is done by Event loop. In JavaScript, the non-blocking concurrency model is based on the concept of an event loop, which allows the execution of asynchronous code without blocking the main thread.

JS Engine

EXECUTION CONTEXT

V8 (JavaScript engine)

In JavaScript, an execution context is an abstract concept that describes the environment in which code is executed.

Each time a function is called, a new execution context is created for that function. Additionally, a global execution context is created when the script is first executed, representing the top-level scope of the code. When a function is invoked, a new execution context is pushed onto the call stack, and when the function returns, its execution context is popped off the stack.

Example of execution context: code)

And result will be:

Starting program
Entering foo
Entering bar
Entering baz
Leaving baz
Leaving bar
Leaving foo
Program finished

Here's what happens step by step:

  • The program starts, and the console.log('Starting program') is executed first.
  • The foo() function is called, and it's added to the call stack.
  • Inside foo(), the bar() function is called, and it's added to the call stack on top of foo().
  • Inside bar(), the baz() function is called, and it's added to the call stack on top of bar().
  • Inside baz(), there are no more function calls, so baz() executes and is removed from the call stack.
  • Since baz() finished, the control goes back to bar(), which then executes the remaining code and is removed from the call stack.
  • Since bar() finished, the control goes back to foo(), which then executes the remaining code and is removed from the call stack.
  • Finally, foo() finished executing, and the control goes back to the global context, where the console.log('Program finished') is executed.

The call stack keeps track of the active functions and their execution contexts. When a function is called, its context is pushed onto the stack, and when it returns, its context is popped off the stack. This process continues until all the functions have completed their execution and the stack becomes empty.

It follows the Last-In-First-Out (LIFO) principle, meaning that the most recently called function is the first to be executed and completed before moving on to the next one.

The three main components of an execution context are:

  1. Variable Environment: Every time a function is called or a block of code is executed, a new execution context is created. Each execution context has its own Variable Environment, which represents the local scope of that particular function or block of code. This ensures that variables declared inside a function or block do not interfere with variables in other parts of the code.

    When a variable is accessed or a function is called, JavaScript searches for it first in the current Variable Environment's Record. If the variable or function is not found there, it will look into the Outer Environment Reference and continues searching in the parent scope, creating a chain of Variable Environments known as the "scope chain."

  2. Scope chain or Lexical Environment: The scope determines where these identifiers (variables, functions, etc.) are accessible and where they are not. JavaScript has two main types of scope:

    • Global Scope: Variables declared outside any function or block have global scope. They are accessible from anywhere in the code, including other functions and blocks. code)

    • Local Scope: Variables declared within a function or block have local scope. They are accessible only within the function or block in which they are declared. code)

    • Block Scope: Block scope refers to the visibility and accessibility of variables and functions within a specific block of code, typically delimited by curly braces {}. Before the introduction of ES6 (ECMAScript 2015), JavaScript only had function-level scope, but ES6 introduced the let and const keywords, which allow variables to have block-level scope. code)

      When a variable is referenced in a JavaScript program, the JavaScript engine looks for that variable's value in the current scope. If it doesn't find it there, it moves up the scope chain until it finds the variable or reaches the global scope. This process of searching for a variable in the scope chain is known as the "Scope Chain. code)

      Hoisting is explained in this blog

  3. This Binding: In JavaScript, the this keyword is a special variable that refers to the context of the current execution. Its value depends on how and where it is used. Understanding how this behaves is essential for writing effective and maintainable JavaScript code. The value of this can be determined by the way a function is called:

    • Global context: When this is used outside of any function or object, it refers to the global object, which is typically window in browsers or global in Node.js.
    • Function context: When this is used within a regular function (not an arrow function), its value depends on how the function is called:
    • Function Invocation: If a function is called as a standalone function, this will point to the global object (in non-strict mode) or undefined (in strict mode). code)
    • Method Invocation: When a function is called as a method of an object, this will refer to the object that owns the method. code)
    • Arrow functions: Unlike regular functions, arrow functions do not have their own this context. Instead, they inherit the value of this from the surrounding lexical scope. code)
    • Constructor invocation: When a function is used as a constructor with the new keyword, this refers to the newly created instance. code)
    • Explicit binding: JavaScript provides three methods (call, apply, and bind) to explicitly set the value of this when calling a function. code)

Every execution context has two phases:

  • Creation Phase: In this phase, the JavaScript engine first creates a variable object (also known as Activation Object) that contains all the variables, function declarations, and arguments defined inside the context. In the creation phase, variables are initialized with a default value of undefined, while functions are already fully defined. The scope chain (the hierarchy of parent scopes in nested functions) is also determined in this phase. The this value is determined in the creation phase as well, based on the calling context of the function.

  • Execution Phase: In this phase, the JavaScript engine starts executing the code line by line. Here, the values of variables are updated with their actual values as they are encountered in the code.

MEMORY HEAP

Before we explain memory heap we need to understand that in JavaScript, there is a fundamental distinction between primitive data types and objects when it comes to how they are stored in memory. Understanding this difference is crucial for efficiently managing memory and optimizing code performance.

  • Primitives: JavaScript has six primitive data types: undefined, null, boolean, number, string, and symbol. When you create a variable to hold a primitive value, the value itself is directly stored in the variable. Primitives are immutable, which means their values cannot be changed after they are created. Each primitive value is assigned a fixed amount of memory based on its data type. code) If you assign a new value to a variable that previously held a primitive, the old value is discarded, and the new value is stored in its place.

  • Reference types: Everything except the primitives are Objects in JavaScript, including objects, arrays and functions, and they are called reference data types in JavaScript. When you create an object, the variable stores a reference to the memory location where the object is stored, rather than the object's value itself. Objects are mutable, and their values can be modified after creation.

    code) In this case, person contains a reference to the memory location where the object { name: "Alice", age: 30 } is stored. When assigning an object to a new variable or passing it as an argument to a function, the reference is copied, not the object itself. As a result, multiple variables can refer to the same object in memory.

    To Recap

    1. All Objects are stored in MEMORY HEAP
    2. Primitives are stored in EXECUTION CONTEXT in which they are declared

    V8 (JavaScript engine)

    So Memory heap is a region in the computer's memory used by the JavaScript engine to allocate memory dynamically during the runtime of a program. It is where objects are stored.

    Key characteristics of the JavaScript memory heap:

    • Dynamic Allocation: Unlike some low-level programming languages where memory must be manually allocated and deallocated, JavaScript's memory heap automatically manages memory allocation. When objects or data structures are created, JavaScript's memory manager finds a suitable space in the heap to store them.

    • Garbage Collection: JavaScript uses a garbage collector to automatically deallocate memory that is no longer needed. The garbage collector periodically scans the memory heap for objects that are no longer accessible or referenced by the program. When an object is no longer reachable, the garbage collector frees up the memory it was occupying, making it available for future allocations.

    • Object Allocation: Objects in JavaScript, including arrays and functions, are stored in the memory heap. When you create an object, either through object literals or constructor functions, memory is allocated in the heap to store the object and its properties.

    • Reference Counting: JavaScript's garbage collector primarily uses a technique called "reference counting" to determine which objects are no longer in use. Each object in memory has a reference count that keeps track of the number of references to it. When the reference count of an object drops to zero, meaning no variable or data structure refers to it, the garbage collector identifies it as garbage and reclaims its memory.

    • Memory Leaks: Although JavaScript has automatic garbage collection, memory leaks can still occur if objects are unintentionally kept in memory despite no longer being needed. This can happen when circular references are present or when developers are not careful with their code, leading to retained references to objects that should have been released.

Asynchronous JavaScript will be explained in a separate blog post

Share:

Accelerating Digital Success. Experience the future of web development – faster, smarter, better. Lets innovate together.

©2024 Dreit Technologies | All rights reserved

Contact

  • +38 64 577 3034
  • Serbia
  • Marka Pericina Kamenjara 11A, Ruma
  • Contact us