# Exploring the JavaScript Engine
###### tags: `JavaScript` `JavaScript Engine` `Rendering Engine` `Browser Engine` `GUI` `V8` `Blink` `Chromium` `Webkit` `Event Driven` `Web Technology` `Operating System` `Memory Management` `API` `System Design` `Cross Platform` `Software` `App` `Optimization` `JIT` `Compiler` `CSS`
Explore the JavaScript core engine in the browser/Node.js, event-driven architecture design, sandboxing strategies, APIs for web standards and browser/Node.js infrastructures (with virtual methods and low-level OS supports), and other implementation concepts, to find out how web apps work and how graphics gets rendered in browsers, JavaScript gets executed, and how a process in a confined system collaborates with outside facilities and services securely while effectively.
:::warning
**This is an overview of JavaScript Engines in Browsers.**
:arrow_right: For more detailed contents, please visit: [我所不知道的 JavaScript (上):理論篇](https://hackmd.io/@shibarashinu/BkAHFociR)
:::
## JavaScript Engine

### Execution Context
1. **Global Execution Context**
Before the JavaScript execution, JavaScript engine will set up a global environment for user script, it has two mainly features:
1. Create a *Global Object* as the root (e.g, `window` for a web browser, `global` for node.js runtime).
2. Bind `this` to current execution context, which is the *Global Object* (e.g., `window`, `global`).
2. **Function Execution Context**
When entering a function scope, JavaScipt engine creates a local execution context environment:
1. **Normal Functions (as Declarations or Expressions):**
1. Find the function declaration within the any valid scope.
2. Once found, pass *arguments* to the function.
3. Bind `this` to the *caller's execution context* - ***anonymous** (eventually pointing to the global object)* or ***function object***. (by analogy with `func.call(caller_context)` prototype's method)
```javascript=
"use strict" // "use strict": anonymous becomes "undefined"
const obj = {
/*
* obj.print_this is declared by "obj"
* (function actual definition is at the global scope)
*/
print_this,
print_this2: function() {
/*
* No caller is bound to this inner_func() call,
* (that is, caller anonymous),
* it will be called by "global object" or "undefined",
* depending on runtime default behavior & mode.
*/
const inner_this = inner_func();
// (function definition will be hoisted to the scope's top)
function inner_func() {
return this;
}
console.log(`this: ${this}`,
`inner_this: ${inner_this}`);
},
};
// print_this() is declared by the "global object"
function print_this() {
console.log(`this: ${this}`);
}
// var_print_this is declared by the "global object"
const var_print_this = obj.print_this;
obj.print_this(); // obj
obj.print_this2(); // obj, anonymous (global object or undefined)
print_this(); // anonymous (global object or undefined)
var_print_this(); // anonymous (global object or undefined)
```
To ensure function inside object always bound "this" to this object, we can make use of `class` to initialize the member function that it always be called by this `class` instance.
```javascript=
class Obj {
constructor() {
this.bound_print_this = this.bound_print_this.bind(this);
}
// bound_print_this will always be called by Obj's instance
bound_print_this() {
console.log(`this: ${this}`);
}
print_this() {
console.log(`this: ${this}`);
}
}
const obj = new Obj();
// bound_print_this is declared by "global object"
const bound_print_this = obj.bound_print_this;
// print_this is declared by "global object"
const print_this = obj.print_this;
obj.bound_print_this(); // obj
obj.print_this(); // obj
bound_print_this(); // obj
print_this(); // anonymous (global object or undefined)
```
2. **Arrow Functions (as Expressions Only):**
1. The arrow functions are used as the variable declarations with `var`, `let`, and `const`. They comply with those rules just like normal variables.
2. No *arguments* are passed to the function.
3. No extra `this` execution context is created, meaning it undertakes the existed `this` from ==the execution's context which declares it==, NOT the caller's.
```javascript=
const obj = {
/*
* obj.print_this is declared by "global object",
* & so are its inner_func() & the arrow function
*/
print_this: () => {
let arrow_func_this1, arrow_func_this2;
const inner_arrow_func = () => arrow_func_this1 = this;
inner_arrow_func();
(()=> arrow_func_this2 = this)();
console.log(`this: ${this}`,
`arrow_func_this1: ${arrow_func_this1}`,
`arrow_func_this2: ${arrow_func_this2}`);
},
/*
* function print_this2() is declared by "obj",
* & so are its inner_func() & the arrow function
*/
print_this2: function() {
let arrow_func_this1, arrow_func_this2;
const inner_arrow_func = () => arrow_func_this1 = this;
inner_arrow_func();
(()=> arrow_func_this2 = this)();
console.log(`this: ${this}`,
`arrow_func_this1: ${arrow_func_this1}`,
`arrow_func_this2: ${arrow_func_this2}`);
}
};
// print_this is dclared by "global object"
const print_this = obj.print_this;
// print_this2 is dclared by "global object"
const print_this2 = obj.print_this2;
obj.print_this(); // anonymous (global object or undefined) x3
obj.print_this2(); // obj x3
print_this(); // anonymous (global object or undefined) x3
print_this2(); // anonymous (global object or undefined) x3
```
Refs:
- [Arrow Functions vs Regular Functions in JavaScript – What's the Difference? - Dillion Megida](https://www.freecodecamp.org/news/the-difference-between-arrow-functions-and-normal-functions/)
- [[JavaScript] Javascript 的執行環境 (Execution context) 與堆疊 (Stack) - itsems](https://medium.com/itsems-frontend/javascript-execution-context-and-call-stack-e36e7f77152e)
- [鐵人賽:JavaScript 的 this 到底是誰? - 卡斯伯's Blog](https://www.casper.tw/javascript/2017/12/12/javascript-this/)
### Runtime JavaScript Engine (with Browser APIs for Example)

1. **Call Stack**: Single-threaded *Main Thread*, execute JavaScript function from main code or event handle.
2. **Memory Heap**: Allocate memory for the variables, functions stacks
3. **Event Loop**: A single-threaded scheduler that determine which *task context* to fire.
Concept:
```javascript=
(function eventLoopRun() {
let time = 0;
while (true) {
if (callStack.empty()) {
eventLoop.microTaskQueueFlush();
eventLoop.taskQueueTick();
let newTime = Date.now();
if (newTime - time > 0.016) { // 60 fps
time = newTime;
eventLoop.microTaskQueueFlush();
eventLoop.renderQueueTick(), eventLoop.render();
}
}
}
})();
```
4. **Callback Queue**: Any kind of *task contexts* that waiting *Event Loop* to fire
- **MicroTask Queue:** If *Call Stack* is empty, it may be fired at **any** endpoint of the *task context* (when fire: microtasks in queue will all flush until *NO* microtask being queued)
Ex:
- Promises (resolve(), reject())
- Browser observers (Mutation observer, Intersection Observer, Performance Observer, Resize Observer)
- **Render Queue:** If 60fps_Times_up => requestAnimationFrame() (rAF) + CSS calc + layout tree + pixel shading (when fire: tasks in queue will flush once, excluded the afterward queues)
- *Event Loop* will rather choose *Task Queue* (left) or *Render Queue* (right) to execute
> [Jake Archibald on the web browser event loop, setTimeout, micro tasks, requestAnimationFrame, ... - JSConf](https://www.youtube.com/watch?v=cCOL7MC4Pl0)

- The main thread walking through layout tree producing layer tree, in order to rasterize page elements in parts.


**Render Flowchart:**


***Event.preventDefault()***
> [Inside look at modern web browser: *Renderer Process - Main / Worker / Compositor / raster Thread* - chrome dev](https://developer.chrome.com/blog/inside-browser-part3/)

- **Task Queue:** If isQueued() & !callStack => may fired at any time
(when fire: one task executed per time)
- Timer events (setTimeout(…), setInterval(…))
- DOM/Web events (onclick, onkeydown, XMLHttpRequest etc)
- Ex. dom.addEventListener('click', ...)

- **Callback Issues:**
- [*State of Zalgo* issues - #Async / Sync issues](#Async--Sync-issues)
- [Tasks vs. microtasks - Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide#tasks_vs._microtasks)
- [In depth: Microtasks and the JavaScript runtime environment - Mozilla](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth#tasks_vs_microtasks)
- User event & window event inconsistent result:
***User click() Event:***

**vs. *Window click() Event:***

Further Readings:
- [Concurrency model and Event Loop - Mozilla](https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop)
- [JavaScript Event Loop And Call Stack Explained - Felix Gerschau](https://felixgerschau.com/javascript-event-loop-call-stack/)
- [JavaScript: How is callback execution strategy for promises different than DOM events callback? - jitendra kasaudhan](https://medium.com/@jitubutwal144/javascript-how-is-callback-execution-strategy-for-promises-different-than-dom-events-callback-73c0e9e203b1)
- [The JavaScript Event Loop: Explained - Erin Swenson-Healey](https://blog.carbonfive.com/the-javascript-event-loop-explained/)
- [EventLoop Explained - JSConf](https://www.youtube.com/watch?v=8aGhZQkoFbQ)
### [Read More] Async Function & Promise in C++
[C++ 執行緒:promise、future、packaged_task 與 async 的使用方法](https://zh-blog.logan.tw/2021/09/26/cxx-thread-promise-future-packaged-task-async-usage/)

### Memory Management

More:
- [:+1: JavaScript's Memory Management Explained - Felix Gerschau](https://felixgerschau.com/javascript-memory-management/)
- [:+1: Visualizing memory management in V8 Engine (JavaScript, NodeJS, Deno, WebAssembly) - Technorage](https://deepu.tech/memory-management-in-v8/)
- [Memory management - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management)
#### Allocation: Stack & Heap
| | Stack | Heap |
|:----------:|:----------------------------------------:|:---------------------------:|
| Content | const, primitive values, references | [...], {...}, function() {} |
| Allocation | fixed /immutable size at *compiled time* | dynamic size at *run time* |
#### Hoisting in Stacks
Hoisting is JavaScript's default behavior of declarations at first but *NOT initialized*, & when referencing the variable JavaScript will start finding the symbol's declaration from local to outside scope.
```javascript=
x = 5; // 2. Find the declaration of "x" & assign 5 to x.
console.log(x); // 3. Get x's value.
var x; // 1. Scan through declarations & hoist them.
```
But ***functions declarations*** do *initialized*.
```javascript=
func('AAAA'); // [func] AAAA
var_func('AAAA'); // var_func is not a function
function func(str) {
console.log(`[func] {str}`);
}
var var_func = function(str) {
console.log(`[var_func] {str}`);
}
```
:::warning
**Note:** Variables defined with ***let*** and ***const*** are hoisted to the top of the block, but not *initialized* & cannot access before initialization.
:::
:::warning
**Note:** JavaScript under ***"use strict"*** mode variables can't be used if they are not declared.
:::
#### Scope in Stacks
[Control flow and error handling - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Control_flow_and_error_handling)
> JavaScript hadn't introduced *local scope* until ECMAScript2015 version (*let* keyword comes into the play). Whereas the *var* variables will be globally the same even if it is defined in a scope, which will get opposite answer from C. For instances:
```javascript=
var x = true;
{
// for var, closure is redundant
var x = false;
}
console.log(x); // x = false
```
Related Readings:
- [JavaScript Hoisting - w3schools](https://www.w3schools.com/js/js_hoisting.asp)
- [[JavaScript] Javascript 中的 Hoisting(提升):幫你留位子 - itsems](https://medium.com/itsems-frontend/javascript-hoisting-589488622dd7)
- [[JavaScript] 你應該使用 let 而不是 var 的 3 個重要理由 - realdennis](https://realdennis.medium.com/懶人包-javascript中-使用let取代var的3個理由-f11429793fcc)
#### Prototype & Class
- [Prototype vs Class in JavaScript - Aman Velpula - Turing](https://www.turing.com/kb/prototype-vs-class-in-js)
- [該來理解 JavaScript 的原型鍊了 - TechBridge 技術共筆部落格](https://blog.techbridge.cc/2017/04/22/javascript-prototype/)
- [[JavaScript] 函數原型最實用的 3 個方法 — call、apply、bind](https://realdennis.medium.com/javascript-%E8%81%8A%E8%81%8Acall-apply-bind%E7%9A%84%E5%B7%AE%E7%95%B0%E8%88%87%E7%9B%B8%E4%BC%BC%E4%B9%8B%E8%99%95-2f82a4b4dd66)
#### Shallow Copy
- [Copy array by value - stackoverflow](https://stackoverflow.com/questions/7486085/copy-array-by-value)
### Automatic Garbage collection
*Mark-and-sweep algorithm* marks the objects that aren't reachable as garbage, and sweeps (collects) them afterward. Root objects will never be collected.


:::info
**Garbage Collection Issues**
> **Why are garbage collectors bad?**
> A convenient but low-performant mechanism, it needs extra steps to check for unused memory & then take further memory management steps.
In `Go`, on cache key eviction, memory is not immediately freed. Instead, the *garbage collector* runs every so often to find any memory that has no references and then frees it. In other words, instead of freeing immediately after the memory is out of use, memory hangs out for a bit until the *garbage collector* can determine if it’s truly out of use. During *garbage collection*, `Go` has to do a lot of work to determine what memory is free, which can slow the program down.
These latency spikes definitely smelled like *garbage collection* performance impact, but we had written the `Go` code very efficiently and had very few allocations. We were not creating a lot of garbage.
After digging through the `Go` source code, we learned that ==`Go` will force a *garbage collection* run every 2 minutes at minimum. In other words, if *garbage collection* has not run for 2 minutes, regardless of heap growth, go will still force a *garbage collection*==.
We adjusted the *garbage collector*'s frequency by modifying the GC Percent through a service endpoint, but saw no change. The issue was that memory wasn't being allocated quickly enough to trigger more frequent *garbage collection*.
We kept digging and learned the spikes were huge not because of a massive amount of ready-to-free memory, but because **the *garbage collector* needed to scan the entire ++LRU cache++ in order to determine if the memory was truly free from references**. Thus, we figured a smaller ++LRU cache++ would be faster because the *garbage collector* would have less to scan. So we added another setting to the service to change the size of the ++LRU cache++ and changed the architecture to have many partitioned ++LRU caches++ per server.
We were right. With the ++LRU cache++ smaller, *garbage collection* resulted in smaller spikes.
However, reducing the ++LRU cache++ size led to higher 99th percentile latency due to more frequent database loads when cache misses occurred. After testing various cache sizes, we found a workable setting and continued with it, prioritizing other tasks. As `Rust` showed promise in other Discord projects, we decided to port this service to `Rust`, hoping it would resolve latency issues and validate `Rust` as a service language.
`Rust`'s performance benefits from its lack of a *garbage collector* and its memory "ownership" model, which tracks and manages memory at compile time. This ensures immediate deallocation of memory when it's no longer needed, avoiding the latency spikes seen with `Go`. In the `Rust` version of the Read States service, memory is promptly freed when data is evicted from the ++LRU cache++, eliminating runtime delays.
Refs:
- [Why Discord is switching from Go to Rust - Jesse Howarth - Discord](https://discord.com/blog/why-discord-is-switching-from-go-to-rust)
- [1 TRILLION messages - ByteByteGo](https://www.youtube.com/watch?v=jo6U429l3JM)
Relative Topics:
- [我是Redis,MySQL大哥被我坑惨了! - 轩辕的编程宇宙](https://www.youtube.com/watch?v=D16efi0TDIs)
:::
### Memory Leaks (Unused Memory Occupation)
*Be sure to remove the pointers pointing to those resources that are not available to use.*
- **Global variables:**
Variables declared under the root or variables occasionally declared under global context will never get deleted since they was pointed by the root.
> To avoid such surprises, always write JavaScript in strict mode using the "use strict", or specify "\-\-use_strict" flag on Node.js apps.
- **Forgotten timers and callbacks:** Forgetting about timers and callbacks can make the memory usage of your application go up. Especially in Single Page Applications (SPAs).
- **Out of DOM reference:** If DOMs are been called removed, but there still have the pointers to those elements, leading to the leak of memory.
More:
- [Avoiding Memory Leaks in Node.js: Best Practices for Performance - AppSignal](https://blog.appsignal.com/2020/05/06/avoiding-memory-leaks-in-nodejs-best-practices-for-performance.html)
### [Read More] Memory Management in Python
*Similar to Linux Buddy x slab memory management system.*
```
(requested_size > 256kB?
malloc(requested_size) :
(requested_size > 4kB?
arena_alloc :
(used_pools[requested_size / 8] || pool_alloc(requested_size / 8))
&& used_pools[requested_size / 8].alloc()
)
)
```
[【python】内存管理结构初探——我要的内存从哪儿来的? - 码农高天](https://www.youtube.com/watch?v=igB-hkESMeI)
#### Memory Types
- **Block (8B, 16B, 24B, ...):** Fixed size memory blocks reside in the pool.
- **Pool (4kB, same as a Page):** A collection of blocks of the same size, indexed by block size in the UsedPools' map for quick location of space with the desired size.
- **Arena (256kB):** Large memory area that can use for pool_alloc or 4kB ~ 256kB memory use.
## Syncronization in JavaScript
### Async / Sync Issues
> JavaScript never shares data across threads, but parallel events may get into racing condition.
> --- [You Don't Know JS: Async & Performance - gitbook](https://github.com/jkasaudhan/You-Dont-Know-JS/blob/master/async%20%26%20performance/ch1.md)
- *State of Zalgo* issues: [Designing APIs for Asynchrony](https://blog.izs.me/2013/08/designing-apis-for-asynchrony/)
- [Using promises - Timing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#Timing)
- [Callbacks, synchronous and asynchronous - HAVOC'S BLOG](https://blog.ometer.com/2011/07/24/callbacks-synchronous-and-asynchronous/)
## Parallel Programming
### Worker APIs
> A worker thread (in the renderer process) includes **a separate message queue, event loop, and memory space**. Communication between the workers' thread and main thread are done by messages.
- The main thread cooperates with *workers*

- *window* global context vs. *worker* global context

**Example: Forking Processes**
- main.js
```javascript=
var child = require("child_process");
var worker = child.fork("./routine");
worker.on("message", (msg) => console.log("recieve from child", msg));
worker.send("hello from parent");
```
- routine.js
```javascript=
process.on("message", (msg) => {
console.log("recieve", msg);
...
process.send("ok");
})
```
## Inside look at a Web Browser

### Start Browsing
1. **Type something into address bar**
*UI Thread* handles the task.

2. **Trigger a network request & parse**
*UI Thread* calls *Network Thread* to init a TCP/IP connection & do stuff matters the data (Ex. sniffs data type).

> Requesting IP address with domain name via DNS (Domain Name System):
> 
>
> (Source: [Everything You NEED to KNOW About Web Applications - ByteByteGo](https://www.youtube.com/watch?v=_higfXfhjdo))
3. **Pass data to destination**
If it is a file, pass it to *Download File Handler*.
If it is a html, tell *UI Thread* data ready, *UI Thread* then start dispatching task to a new *Renderer Process* (in the meanwhile, the *Network Thread* keeps recieving data).
> *Render Process* provide a secure enclosed sandbox enviroment for executing scripts on tabs.

4. **Commit navigation**
*UI Thread* changes browser's outer look, updating informations.

5. **Parse & render**
Once data starts flowing from *Browser Process* to *Renderer Process*, *Main Thread* in *Renderer Process* start to parse html document and create DOM, layout tree, assign *Worker Threads* as script loaders and intepreters.
Once *Renderer Process* starts running *Event Loop* and decide to pick up *Render Queue Task*, page on screen have getting changing.

6. **Document *onload***
Once *Renderer Process* completes rendering, IPC *Browser Process - UI Thread* stops spinning icon.

ref:
- [Inside look at modern web browser - Chrome Dev](https://developer.chrome.com/blog/inside-browser-part1/)
- [How browsers work - web.dev](https://web.dev/howbrowserswork/)
### Inter Process Communication (IPC)
A process can ask the Operating System to start another process to run different tasks. When this happens, different parts of the memory are allocated for the new process. If two processes need to talk, they can do so by using Inter Process Communication (IPC). Many applications are designed to work this way so that if a worker process get unresponsive, it can be restarted without stopping other processes which are running different parts of the application.

### Save Processes, Save Memory!
Processes have their own private memory space, they often contain copies of common infrastructure (like V8 which is a Chrome's JavaScript engine). This means more memory usage as they can't be shared the way they would be if they were threads inside the same process.
1. **Process Limitation:** Varies depending on how much memory and CPU power your device has, but when Chrome hits the limit, it starts to run multiple tabs from the same site in one process.
2. **Hardware Personalization:** If Chrome is running on powerful hardware, it may split each service into different processes giving more stability, but if it is on a resource-constraint device, Chrome consolidates services into one process saving memory footprint. Similar approach of consolidating processes for less memory usage have been used on platform like Android before this change.
