DEV Community

Cover image for πŸš€How JavaScript Works (Part 1)? Look At JS Engine
Sam Abaasi
Sam Abaasi

Posted on • Updated on

πŸš€How JavaScript Works (Part 1)? Look At JS Engine

Introduction:

Have you ever wondered about the magic that transforms your code into the interactive web pages users adore? What truly unfolds behind the scenes when you engage with a web page? Join me on a journey as we unravel JavaScript's secrets, focusing on its powerhouse, the JavaScript engine.

Demystifying the JavaScript Engine for Developers:

Every web interaction owes its magic to the JavaScript engineβ€”a mysterious wizard that interprets and executes code to create dynamic web experiences. In this article, we'll embark on a journey to demystify JavaScript, showing how it transforms your code into captivating web features. As developers, understanding this is key to mastering web development.

Understanding the JavaScript Engine:

Before we venture into the intricacies of JavaScript, let's familiarize ourselves with the star of the show – the JavaScript engine. A JavaScript engine is a formidable piece of software that takes on the formidable task of interpreting and executing JavaScript code. While different web browsers may employ distinct JavaScript engines, they all adhere to common principles and share the same fundamental goals.

Two Pivotal Phases of a JavaScript Program: Compilation and Execution

As we delve deeper into the inner workings of JavaScript, we encounter two pivotal phases that define the lifecycle of a JavaScript program: compilation and execution. These phases dance in perfect harmony, ensuring that your code runs not only efficiently but also reliably.

From Code to Bytecode (Executable Code):

Our journey into the heart of JavaScript commences with developers crafting lines of code, each intended to perform specific tasks. Yet, as brilliant as human-readable code may be, browsers are mere machines incapable of deciphering the intricacies of our linguistic marvel. Thus, the transformation of JavaScript code into a format that machines can comprehend is essential. This intermediary format is known as bytecode, a compact, lower-level representation of our high-level code.
Code to Bytecode

From Code to Bytecode (Executable Code)

The Role of Compilers in JavaScript: Translating Human Logic into Machine Instructions

To understand the compilation phase of JavaScript comprehensively, it's crucial to grasp the pivotal role played by compilers. A compiler is a fundamental component of JavaScript engines, serving as the bridge that transforms your human-readable code into a form that machines can interpret and execute efficiently.

Understanding Compilers:

At its core, a compiler is a type of software that translates source code written in a high-level programming language (in our case, JavaScript) into a lower-level representation known as bytecode or machine code. This transformation involves several essential steps, collectively known as the compilation process.

1. Lexical Analysis (Lexing or Tokenization):

The compilation process begins with lexical analysis, also referred to as lexing or tokenization. During this phase, the compiler breaks down your JavaScript code into individual tokens. Tokens are the fundamental building blocks of your code and encompass keywords, variables, operators, and other essential elements. This initial step simplifies the code and makes it more manageable for further analysis.

Example:
Consider the following JavaScript code snippet:

function helloWorld() {
  console.log("Hello World!")
}
Enter fullscreen mode Exit fullscreen mode

Lexical analysis breaks it into the following tokens:

[
  { type: 'keyword', value: 'function' },
  { type: 'identifier', value: 'helloWorld' },
  { type: 'punctuation', value: '(' },
  { type: 'punctuation', value: ')' },
  { type: 'punctuation', value: '{' },
  { type: 'identifier', value: 'console' },
  { type: 'punctuation', value: '.' },
  { type: 'identifier', value: 'log' },
  { type: 'punctuation', value: '(' },
  { type: 'string', value: 'Hello World!' },
  { type: 'punctuation', value: ')' },
  { type: 'punctuation', value: '}' }
]
Enter fullscreen mode Exit fullscreen mode

Lexical Analysis

Lexical Analysis (Lexing or Tokenization)

2.Syntax Parsing (Abstract Syntax Tree - AST):

Following lexing, the compiler proceeds to syntax parsing. During this phase, it constructs an Abstract Syntax Tree (AST) based on your code's grammatical structure. The AST serves as a structured representation of your code, capturing the hierarchical relationship between different elements.

Example:
For the code snippet

function helloWorld() {
  console.log("Hello World!")
}
Enter fullscreen mode Exit fullscreen mode

the resulting AST might appear as follows:

FunctionDeclaration
β”œβ”€β”€ id: Identifier (name: "helloWorld")
β”œβ”€β”€ params: []
└── body: BlockStatement
    └── body: 
        └── ExpressionStatement
            └── expression: CallExpression
                β”œβ”€β”€ callee: MemberExpression
                β”‚   β”œβ”€β”€ object: Identifier (name: "console")
                β”‚   β”œβ”€β”€ property: Identifier (name: "log")
                β”‚   └── computed: false
                └── arguments: 
                    └── Literal (value: "Hello World!")
Enter fullscreen mode Exit fullscreen mode
  • The FunctionDeclaration node signifies the declaration of a function.
    • id: Identifier (name: "helloWorld") denotes the function name.
    • params: An empty array [] signifies that there are no parameters for this function.
    • body: BlockStatement represents the function's body.
    • body: BlockStatement represents the block of code inside the function.
      • type: ExpressionStatement signifies an expression statement.
      • expression: CallExpression represents a function call.
        • callee: MemberExpression represents the member access of the console.log function.
        • object: Identifier (name: "console") is the object (console) on which the function is called.
        • property: Identifier (name: "log") is the function being called.
        • computed: false indicates that the property is not computed.
        • arguments: An array containing a single element:
        • Literal (value: "Hello World!") is the argument passed to the console.log function, a string literal with the value "Hello World!".

Syntax Parsing

Syntax Parsing (Abstract Syntax Tree - AST)

Errors Occur During the Parsing Phase:

The parsing phase serves as the stage where errors come into the spotlight. These errors can be categorized into several types:

  • Syntax Error: The JS engine first parsing the entire program before any of it is executed. Consider this example:
var greeting ="Hello";
console.log(greeting)
greeting = ."Hi";
//SyntaxError: unexpected token '.'
Enter fullscreen mode Exit fullscreen mode

This program produce no output, instead throws a SyntaxError, since SyntaxError happens after well-formed console.log(..) statement, if JS was executing top-down line by line, one would expect the "Hello" message being printed before the SyntaxError being thrown.

  • Early Errors:Consider this code:
console.log("Howdy");

saySomething("Hello", "Hi");
// Uncaught SyntaxError: Duplicate parameter name
// not allowed in this context
function saySomething(greeting, greeting) {
   "use strict";
   console.log(greeting);
}
Enter fullscreen mode Exit fullscreen mode

The SyntaxError here is thrown before the program is executed because saySomething(..) function have duplicate parameter names and strict-mode opted in it.Have duplicate parameter always been allowed in non strict-mode.
Although the error thrown is not a syntax error in the sense of being malformed strings of tokens (like ."Hi" in previous example), but in strict-mode is nonetheless required by the specification to be thrown as an "early error" before any execution begins.

  • Hoisting Error:Consider this example:
function saySomething() {
   var greeting ="Hello";
   {
     greeting = "Howdy";
     let greeting = "Hi";
     console.log(greeting);
   }
}
saySomething()
// ReferenceError: cannot access 'greeting' before
// initialization
Enter fullscreen mode Exit fullscreen mode

Here ReferenceError occurs from the line with the statement greeting = "Howdy", whats happening is that the greeting variable for that statement belongs to the declaration on the next line let greeting = "Hi", rather than to the previous var greeting = "Hello" statement.
Because JS engine processed this code in an earlier pass and set up all the scopes and their variable associations, know at which line the error is thrown.

3. Bytecode Generation:

Finally, after the transformation and optimization processes, the compiler generates bytecode. Bytecode is a compact, lower-level representation of your code's logic. It's an intermediary form that strikes a balance between human readability and machine executability. Bytecode is crucial for efficient execution by the JavaScript engine's virtual machine.

Just-In-Time (JIT) Compilation: A Performance Boost

In the world of JavaScript execution, JIT (Just-In-Time) Compilation is a silent hero. It dynamically optimizes your code just before execution, resulting in remarkable performance improvements. While we won't dive deep into JIT compilation in this article, it's worth noting that modern JavaScript engines employ this technique to make your web applications more responsive and efficient.

JIT

From Code to Bytecode with JIT

Transitioning to the Next Article: Understanding JavaScript's Scope

With a solid grasp of how compilers translate JavaScript code into bytecode, we're now well-prepared to delve into the exciting world of execution. This is where your code comes to life, interacts with the web page, and responds to user actions. But our journey doesn't end here. In the upcoming articles, we'll continue to explore the fascinating landscape of JavaScript.

In the next article, we'll venture into the realm of "Scope." Scope is a fundamental concept in JavaScript that defines where variables and functions are accessible within your code. Understanding scope is like unlocking a superpower that allows you to control how data is shared and manipulated, leading to more efficient and maintainable code.

Stay tuned for our exploration of JavaScript's scope, where we'll unravel the mysteries of global scope, function scope, block scope, and lexical scope. It's a journey that will empower you to write code that's not only functional but also elegant and organized. Join us on this captivating journey as we dive deeper into the heart of JavaScript's inner workings.

This transition sets the stage for the next article's topic, "Scope." If you have any further adjustments or specific points you'd like to include, please let me know!

If you have any questions or specific points to discuss, please feel free to reach out!

Sources:

Kyle Simpson's "You Don't Know JS: Scope & Closures"
MDN Web Docs - The Mozilla Developer Network (MDN)
Official Documentation of JavaScript Engines - V8 , SpiderMonkey, ChakraCore and others.

Top comments (2)

Collapse
 
dealwith profile image
Gleb Krishin

Your series is a great job, thank you!

Collapse
 
samabaasi profile image
Sam Abaasi

Thank you πŸ’πŸ’