PUBLISHED JANUARY 12, 2024
The v8 Engine and the difference Between NodeJS and the browser
In this series part, we explore the V8 engine and how it interacts with nodeJS. We also discuss node’s event loop and uncover the mystery behinds node’s ability to handle concurrent operations.
Prerequisite
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is the engine that powers Google Chrome. It's the thing that takes our JavaScript and executes it while browsing with Chrome.
V8 implements ECMAScript and WebAssembly, and runs on Windows 7 or later, macOS 10.12+, and Linux systems that use x64, IA-32, or ARM processors.
V8 compiles and executes JavaScript source code, handles memory allocation for objects, and garbage collects objects it no longer needs, which is one of the keys to its high performance.
Things to note:
JavaScript is commonly used for client-side scripting in a browser, for example to manipulate Document Object Model (DOM) objects. The DOM is not, however, provided by the JavaScript engine but instead by a browser.
If Chrome is our browser, then Google Chrome provides the DOM and V8 provides all the data types, operators, objects and functions specified in the ECMA standard.
The two combined results in the high performant, seamless modern web experience we all enjoy.
Since V8 is a standalone engine as we highlighted above, it presented a great opportunity for nodeJS. V8 was chosen to be the engine that powered NodeJS back in 2009, and as the popularity of NodeJS exploded, V8 became the engine that now powers an incredible amount of server-side code written in JavaScript.
There are other JavaScript engines of course, but we are not going to cover those in this series, but if you’re interested, then here are a few you can checkout:
- ➢Firefox has SpiderMonkey
- ➢Safari has JavaScriptCore (also called Nitro)
- ➢Edge was originally based on Chakra but has more recently been rebuilt using Chromium and the V8 engine.
and many others.
Performance and Compilation
In this V8 overview, we will of course ignore the implementation details of V8 but you can find it in their documentation website V8 official site .
V8 is always evolving, just like the other JavaScript engines around, to speed up the Web and the NodeJS ecosystem.
JavaScript has historically been considered an interpreted language, but modern JavaScript engines (like V8) no longer just interpret JavaScript, they compile it.
V8 internally compiles JavaScript with just-in-time (JIT) compilation to speed up the execution.
Just-In-Time (JIT) Compilation: V8's JIT compilation contributes to NodeJS’s performance by translating JavaScript into optimized machine code.
`Why machine code ? You may ask.`
Well for those who, like myself, didn’t get a computer science degree, machine code, also known as `machine language`, is the elemental language of computers. Native machine code execution is generally faster than interpreting or executing bytecode.
By compiling JavaScript directly to machine code, V8 can achieve better performance by leveraging the low-level capabilities of the host machine's hardware.
Drawbacks
- ➢Not Ideal for CPU-Intensive Tasks: While perfect for I/O-bound tasks, Node.JS may not be the best choice for CPU-intensive operations due to its single-threaded nature.
- ➢Learning Curve: Developers accustomed to traditional synchronous programming may face a learning curve when adapting to the asynchronous, event-driven paradigm.
NodeJS operates on a single thread, which might sound limiting, but then the V8 engine steps in, making the magic happen.
Let's attempt to demystify the jargon and understand how NodeJS manages to be both single-threaded and handle numerous tasks concurrently.
Keep these jargons in mind as you through:
- ➢Callback: it is a special type of function passed as an argument to another function.
- ➢Event Loop: it is a loop that runs as long as your NodeJS application is up and running.
Event Queue: It is a sort of temporary storage provided to the runtime environment by your machine, where all in-coming async processes are kept while the node is busy.
When node is done, the event loop picks up any callback from async processes and sends them to node for execution, depending on the schedule time set for the operation.
Single-Threaded & Event-Driven: NodeJS is single-threaded, meaning it uses a single execution thread for processing all of its tasks. However, its real charm lies in being event-driven.
Imagine a DJ juggling multiple music requests from his huge audience. Similarly, NodeJS handles various operations concurrently through a dance of events, ensuring that it's never idle.
V8 Engine execution Power: As we already saw in the previous section, V8 is responsible for interpreting and executing JavaScript code with remarkable speed.
It utilizes just-in-time (JIT) compilation to convert JavaScript into machine code, ensuring swift execution of tasks. NodeJs takes full advantage of that.
Event Loop & Non-Blocking Magic: NodeJS employs an event loop, a continuous process checking for events and executing associated callbacks.
When an asynchronous task, like reading a file or making an API call, is initiated, NodeJS doesn't wait around.
Instead, it delegates the task to the event loop and moves on to the next job. This non-blocking behavior ensures optimal resource utilization.All async operations in the nodeJS runtime are handled by libuv.
Libuv is a cross-platform open-source library written in C. In the NodeJS runtime, its role is to provide support for handling asynchronous operations.
Event Queue: Asynchronous tasks once completed, place their callbacks in the event queue. The event loop then picks these callbacks and executes them. This orderly queueing ensures that tasks are handled efficiently, maintaining the rhythm of the system.
Ready for some actual action? Got you 🤓!
Let’s demonstrate how NodeJs handles asynchronous tasks, event loop, and concurrent execution in a single-threaded environment.
// Step 1: Synchronous Execution
console.log('Start of the script');
// Step 2: Asynchronous Task
setTimeout(() => {
console.log('Async task completed after 2 seconds');
}, 2000);
// Step 3: More Synchronous Execution
console.log('Continuing with synchronous tasks');
// Step 4: Another Asynchronous Task
setTimeout(() => {
console.log('Another async task completed after 1 second');
}, 1000);
// Step 5: Finishing Synchronous Execution
console.log('End of the script');
Lets break it down:
- ➢Synchronous Execution: Node starts executing the script synchronously. The first log statement is executed.
- ➢Asynchronous Task (Event Delegation): The setTimeout function is asynchronous. It delegates the execution of the provided callback function (the async task) to the event loop after the specified time (2 seconds in this case).
- ➢More Synchronous Execution: While waiting for the asynchronous task, NodeJS continues with synchronous tasks. It logs another statement.
- ➢Another Asynchronous Task: Another setTimeout function is used to demonstrate multiple asynchronous tasks. It delegates the second async task to the event loop after 1 second.
- ➢Finishing Synchronous Execution:The script continues with the remaining synchronous tasks. The last log statement is executed.
- ➢Event Loop in Action: When node is done i.e. the Callstack is empty, the event picks up the first Callback in the event queue, sends it to the Callstack to be executed and the cycle continues until the event queue is empty.
Now, let's see the output sequence:
Start of the script
Continuing with synchronous tasks
End of the script
Async task completed after 2 seconds
Another async task completed after 1 second
Explanation of the Output:
- ➢The synchronous parts execute first.
- ➢The first async task completes after 2 seconds, printing its log.
- ➢The second async task completes after 1 second, printing its log.
Let see what exactly is going during this execution:
This example illustrates the event loop in action, allowing the execution of asynchronous tasks without blocking the main thread.
The order of log statements shows how NodeJS efficiently handles concurrent operations within a single-threaded environment.
If you want to try it for yourself, change the schedule time in the setTimeout function and see the results.
Conclusion
To conclude, NodeJS, powered by the V8 engine, is a game-changer in server-side development. Its asynchronous, event-driven architecture offers unprecedented efficiency, making it a top choice for building scalable and performant applications.
Understanding the dynamics of NodeJS and V8 empowers developers to leverage their capabilities effectively.
In this series part, we connected all the dots together; nodeJS, V8 engine, Libuv. There is more to node architecture than this beginner overview. For more you can visit the node page.
In the next part, we'll explore "The npm package manager".
Chapter 1 , Part 1 : Introduction to NodeJS
In this series part, I introduce nodeJS and some technical concepts associated with it. I also show how easy it is to setup and start a simple nodeJS web server.
Chapter 1 , Part 2 : How to Install and Setup NodeJS
In this series part, I run you through the various ways to install nodeJS. I also discuss how to install nvm and use it to switch between different node versions.
Chapter 1 , Part 3 : How much JavaScript do you need to learn NodeJS
In this series part, we explore the nuanced relationship between JavaScript and NodeJS, highlighting some subtle distinctions between the two environments.
Chapter 1 , Part 4 : The v8 Engine and the difference Between NodeJS and the browser
In this series part, we explore the V8 engine and how it interacts with nodeJS. We also discuss node’s event loop and uncover the mystery behinds node’s ability to handle concurrent operations.
Chapter 1 , Part 5 : NPM, the NodeJS package manager
Discover the essentials of npm, the powerful package manager for Node.js. Learn installation, management, publishing, and best practices
Chapter 1 , Part 6 : NodeJS in Development Vs Production
Explore how Node.js behaves differently in development and production environments. Learn key considerations for deploying Node.js applications effectively.
Chapter 2 , Part 1 : Asynchronous Flow Control
In this series part, we'll explore various aspects of asynchronous flow control in Node.js, from basic concepts to advanced techniques.
Chapter 2 , Part 2 : Blocking vs Non-blocking I/O
Explore the differences between blocking and non-blocking I/O in Node.js, and learn how to optimize performance and scalability.
Chapter 2 , Part 3 : Understanding NodeJS Event loop
Exploring the Node.js event loop by understanding its phases, kernel integration, and processes enabling seamless handling of asynchronous operations in your applications.
Chapter 2 , Part 4 : The NodeJS EventEmitter
Explore the power of Node.js EventEmitter: an essential tool for building scalable and event-driven applications. Learn how to utilize it effectively!
Chapter 3 , Part 1 : Working with files in NodeJS
Gain comprehensive insights into file management in Node.js, covering file stats, paths, and descriptors, to streamline and enhance file operations in your applications.
Chapter 3 , Part 2 : Reading and Writing Files in NodeJS
Uncover the fundamentals of reading and writing files in nodeJS with comprehensive examples and use cases for some widely used methods.
Chapter 3 , Part 3 : Working with Folders in NodeJS
Unlock the secrets of folder manipulation in Node.js! Explore essential techniques and methods for working with directories efficiently
Chapter 4 , Part 1 : Running NodeJS Scripts
Master the command line interface for executing nodeJS scripts efficiently. Learn common options and best practices for seamless script execution
Chapter 4 , Part 2 : Reading Environment Variables in NodeJS
Learn how to efficiently manage environment variables in nodeJS applications. Explore various methods and best practices for security and portability
Chapter 4 , Part 3 : Writing Outputs to the Command Line in NodeJS
Learn essential techniques for writing outputs in nodeJS CLI. From basic logging to formatting and understanding stdout/stderr.
Chapter 4 , Part 4 : Reading Inputs from the Command Line in NodeJS
Learn the various ways and strategies to efficiently read command line inputs in nodeJS, making your program more interactive and flexible.
Chapter 4 , Part 5 : The NodeJS Read, Evaluate, Print, and Loop (REPL)
Explore the power of nodeJS's Read, Evaluate, Print, and Loop (REPL). Learn how to use this interactive environment for rapid prototyping, debugging, and experimentation.
Chapter 5 , Part 1 : Introduction to Testing in NodeJS
Discover the fundamentals of testing in nodeJS! Learn about testing types, frameworks, and best practices for building reliable applications.
Chapter 5 , Part 2 : Debugging Tools and Techniques in NodeJS
Explore essential debugging tools and techniques in Node.js development. From built-in options to advanced strategies, and best practices for effective debugging.
Chapter 6 , Part 1 : Project Planning and Setup
Discuss the planning and design process for building our interactive file explorer in Node.js, focusing on core features, UI/UX design, and implementation approach and initial setup.
Chapter 6 , Part 2 : Implementing Basic functionalities
In this guide, we'll implement the basic functionalities of our app which will cover initial welcome and action prompts.
Chapter 6 , Part 3 : Implementating Core Features and Conclusion
In this guide, we'll complete the rest of the more advanced functionalities of our app including, create, search, sort, delete, rename and navigate file directories.