- Two Pivotal Phases of a JavaScript Program: Compilation and Execution
- From Code to Bytecode (Executable Code)
- The Role of Compilers in JavaScript: Translating Human Logic into Machine Instructions
- Understanding Compilers
- Lexical Analysis (Lexing or Tokenization)
- Syntax Parsing (Abstract Syntax Tree - AST)
- Bytecode Generation
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):
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!")
}
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: '}' }
]
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!")
}
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!")
- 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!".
-
-
-
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 '.'
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);
}
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
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.
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)
Your series is a great job, thank you!
Thank you ππ