# 我所不知道的 JavaScript (下):應用篇
###### tags: `JavaScript` `JavaScript Engine` `Java` `JVM` `Python` `C` `C++` `Rust` `Runtime` `System Design` `App` `API` `ABI` `Web Technology` `Event Driven` `Cross Platform` `AOT` `JIT` `Compiler` `V8` `TurboFan` `Compiler Design` `Optimization` `Security` `Operating System` `Linux` `Kernel` `Software` `Automata` `UNIX`
記錄多個 JavaScript 語言背後設計哲學、JavaScript 系統環境架構、程式行為與最佳化、ECMAScript 擴充、各技術規範標準和相依平台 API 實作之討論。
:::info
:arrow_right: 上篇:[我所不知道的 JavaScript (上):理論篇 - shibarashinu](https://hackmd.io/@shibarashinu/BkAHFociR)。
:::

> ~~標題剽竊~~ 標題參考自 [「你所不知道的 C 語言」系列講座 - jserv](https://hackmd.io/@sysprog/c-programming)。
## JavaScript Standard
### Execution Contexts
任何有關此次 function call 的參考要素都記載於此,以此塑造「執行環境」以提供 ==runtime 函式程序== 必要協助,通常與 call stack 上的 stack frame 綁定。例如: Scope、Declaration、函式相關資料。
> 銜接靜態 program 和動態 process 之間缺乏的要素。
:::info
**Execution Context vs. Call Stack**
- **Memory Stack:**
搭配 Register 作為 Von Neumann 結構的記憶單元。
- **Execution Context:** 應用狀態機之動態記憶 ==系統環境== 資料 (向開發者隱藏)。
- **Call Stack:** 應用狀態機之動態記憶 ==使用者程式== 資料 (向使用者隱藏)。
> 由面向開發者的「程式語言」包裝,再由面向實際處理的「執行系統」 (e.g., Operating Systems, Runtime Systems) 實作,一同合作實現計算機功能,完成任務。
雖在 runtime 機器中,execution context 常與 stack frame 一塊實作,但各為不同目的所存在,應確實區分兩者用途。
更多:
- [JavaScript Execution Context and Lexical Environment Explained. - Vince Llauderes - HackerNoon](https://hackernoon.com/javascript-execution-context-and-lexical-environment-explained-528351703922)

:::
:arrow_right: 詳見: [Exploring the JavaScript Engine - shibarashinu](https://hackmd.io/@shibarashinu/r1XdprVCj)
### Scope
當前函數執行的 scope 環境,即目前程式 execution context 的作用域、權限、程序下所能觸及的變數資源的「可視範圍」,如: 可否定位目標變數以做數值 bindings。
> 掌握程式語言的變數 scope 行為對程式開發、操控和執行效能有重大影響。
[Scope - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Scope)
:::danger
**注意: JavaScript 作為一動態語言,必須小心與其他語言在 Lexical Scope、Variable Declaration 和 Runtime 等處理手法上的不同**
如 *hoisting*、*execution context* 與 *this*、*first-class function*、*closure* 概念等。
例如:
- C++:
- **Scope:** 每一個 block 都視為是新的局部 scope 作用域。
> JavaScript 在 ECMAScript 2015 引入 `let` / `const` 後才有此種 block scope 概念,不然原 `var` 只論及 function scope 作用域參考!
- **Closure (Lambda Function's Capture):** 可選擇 Pass by Value 或 Reference、選擇性傳遞、能否修改 (mutable),C++ 中的匿名函數閉包參數傳遞比起 JavaScript 和 Python 有更多可選控制。
```cpp=
[&] () {}
```
- Python:
- **Scope:** 沒有 block scope 只有 function scope 作用域!所以在同一 function 內的所有變數都是函式中可被參照到的對象。
> 行為上和在 JavaScript 中純使用 `var` 雷同。
```python=
def func():
for i in range(10):
k = i
print(i, k) // 9, 9
func()
```
- **Closure:** 由於 Python 沒有宣告變數用的 keyword,所以只能靠在 stack 中從下往上盲找最近的「變數賦值」來判斷是否為 closure 作用域,但是也可使用 `nonlocal` / `global` 來指定要往哪個 scope 找。
參考:
- [【python】详解变量作用域,局部闭包还是全局? - 码农高天](https://www.youtube.com/watch?v=k9D_HYaW_68)
:::
:::danger
**JavaScript 函數 Closure 與其他具 Block Scope 程式語言之比較**
在低階程式語言如 C/C++ 中,變數一旦離開 block scope 即無法存取 (在 stack 上已被 pop 掉),若變數指向 heap 上則必須改以其指標形式操作 (e.g., 在 block 中用 `void*`,block 外必須用 `void**`,因為此時 `void*` 已失效),若硬是要存取已失效的變數則會導致未預期行為 (理論上編譯器會阻止)。
但 JavaScript (和 Python) 具高階語言特色的「closure」機制,規定目標函數可存取的變數 scope 為「函數定義當下的執行 context」的變數 scope 作用域 — 即使原函數以離開該函數環境,==在 call stack 上本應該被釋放的資源,因為有內部函數的 closure 在原函數外有參照,使 JavaScript 引擎在一開始就會分析這些可被 closure 捕獲的變數,並預先將他們配置在 heap 上而非 stack==。
有了此額外的目標函式 context 的 closure 指向這些區域變數,使其在原函式生命週期結束時免於被釋放 (`refernece_count > 0`)。
更多:
- 參考 CPython 上對 Closure 機制的實作: [【python】闭包的实现机制。嵌套函数怎么共享变量的? - 码农高天](https://www.youtube.com/watch?v=Flce9y5Qn38)
:::
#### Closure
*一個函數執行時能存取到的變數 scope 環境。*
考慮以下程式碼:
```javascript=
function Print() {
let inner_value = 0;
function inner_print() {
console.log(`Hello ${inner_value++}`);
}
return inner_print;
}
// inner_print & inner_value still can be accessed!
const print = Print();
print(); // Hello 0
print(); // Hello 1
print(); // Hello 2
```
<!-- :warning: -->
:::warning
在 Symbol.iterator (見下章節) 中將一般函式作為「**自訂可迭代 (user-defined iterables)** 」方法時便可看到 Closure 的實際應用場景。
:::
:::warning
**技巧: 利用 Closure 在 Promise 的 Callback 中參照異步操作前的變數**
透過 closure 實現在具「同步處理」和「異步 Promise」的函式中可存取同一變數 — 我可以先宣告未來「異步 Promise」會執行的 callback 函式變數,再在 Main Thread 中慢慢 ++define 該 callback 程序++,最終再由「異步 Promise」觸發 ++fulfill 該 callback 程序++。
應用:
- RPC 模組開發。
```javascript=
exports.send = function ({ action, data, need_resolve = true }) {
const sendID = client.get_sendID();
client.send({ sendID, action, data });
if (need_resolve)
// Register a promise function & return the RPC result.
return new Promise(res => {
_resolve[sendID] = function (data) {
delete _resolve[sendID];
res(data); // Here the event actually returns Promise's result.
};
});
};
```
:::
相關資源:
- [Closures - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)
- [[Book] You Don't Know JS Yet: Scope & Closures - 2nd Edition - getify](https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/scope-closures/README.md)
### Literals
*"Data that ++literally++ provide in the binaries (JS script in this context)".*
為存在記憶體中「固定大小且有特別意義、編排」的資料結構,可被有意義的 *Expression* 所操作,如: 賦予變數特定的數值。
#### JavaScript 基礎資料型別
:::info
**JavaScript 各型態分類 (Type Categories):**
- **Values:** 程式 semantic 分析使用。
> 
>
> (Source: [Data Class Reference - V8 Docs](https://v8docs.nodesource.com/node-0.8/d1/d83/classv8_1_1_data.html))
- **Primitives:** *Boolean*, *Number*, *BigInt*, *String*, *undefined*, *null*, *Symbol*。
- **Non-Primitives:** *Object*, *Array*, *Function*。
- **Literals:** 實際運行程式所使用 value 之型態。
- *Array*, *Boolean*, *Numeric (int, float)*, *Object*, *RegExp*, *String*。
其中 semantic values 部份可用 built-in 運算子 `typeof` 查詢:
```javascript
console.log(typeof /regex/); // "object"
```
或由 constructor 鍵值判斷:
```javascript
const arr = [];
console.log(arr.constructor === Array); // true
console.log("".constructor === String); // true
```
[Grammar and types - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Grammar_and_types)
:::
:arrow_right: 延伸在 C++ 相關 semantic 資料型態分類討論: [[C++] Your Rvalue Isn't Meant to Be the Rvalue - shibarashinu](https://hackmd.io/@shibarashinu/By3XL37d0)
#### 程式中的 Literal 關係
literal 概念上屬於 `Rvalue` 型態: *literal* 作為「受質」,可與 *identifier*「繫結」始可被變數、參數等「受體」容器所存取,*literal* 所乘載在記憶體上的實體內容則為的 *value*。
:::warning
**程式編譯角度:**
在編譯過程中,(1) 識別 *identifier (variable)* 和 *literal (value)* 等等 tokens;(2) 檢查程式文句語法;(3) 再將他們繫結成一句有意義的完整行為描述的步驟,分別在 lexical、syntax 和 semantic 分析時產生。
:::
:::warning
**程式執行角度:**
在可執行程式中,「*定義 literal*」等價於「*使用該資料結構的 initializer*」。
:::
:::info
**Identifiers vs. Variables**
*identifier* 用作代表程式碼某項元件的「名字」,以識別、操作特定元件。
*variable* 則是可儲存資料的「元件」,廣義上來說,可被編譯器轉化映射成以下任何 ==可被定址、可做讀寫資料操作的區塊==:
- SRAM: register (間接影響 L1/L2/L3 cache)、interrupt line、controller 控制、buffer 等。
- DRAM: stack、heap、page 等。
- EEPROM: BIOS、UEFI、firmware config 等。
- Storage: frame、block、sector 等。
- I/O Device: driver、socket、buffer 等。
- Circuit: MCU、FPGA 等。
- 其他: 打孔紙、燈泡、真空管等。
即是為 variable 的載體 (GLvalue in C++)。端看使用場景、計算設備架構、描述語言 (IDL、DSL、Devicetree、HDL 等) 和編譯器如何實現一支可利用「variable 來操作元件」做特定用途的程式。
> 好似「Linux VFS」的核心精神。
更多:
- [Binding - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Glossary/Binding)
:::
:::info
**Literals vs. Values**
*value* 為一串無意義的「數值」(i.e. 訊號、映像、數據),不一定是由程式碼直接提供,只要可被 *variable* 儲存的任何 pattern 都可視作 *value*。
> 在 JavaScript 中,所有 *value* 以 *Primitives* 或 *Non-Primitives* (Objects) 形式體現。
*literal* 為儲存在記憶媒介中有意義的「資料結構」,載明在程式碼並在執行時由 runtime 載入。
> 可視作 *literal* 包裹 *value*,賦予其特別意義 (e.g., 特定型態、編解碼方法、用法) 成為有用的資料型態,始可被程式所用。
Refs:
- [Difference between values and literals - StackOverflow](https://stackoverflow.com/questions/18970938/difference-between-values-and-literals)
:::
#### 與其他程式語言之 Literals 比較
在 compiled language 如 C,literals 會被存於與目標檔案系統相依,且與作業系統載入相關的 ABI 相容之++檔案++ / ++程序++區塊中 (i.e., 存放於 ELF 標準中的 *.rodata*, etc. 的 ++section++ / ++segment++);而如 JavaScript 的動態語言則是將 literal 存於 *code text* 中,再動態載入至記憶體 (call stack 或 heap)。
#### Array Literals
:::danger
**JavaScript 中 Array Literals 的特例定義**
JavaScript 允許在陣列中定義空項目 *(empty item)*:
```javascript
const arr = [, "B"]; // [<1 empty item>, "B"]
```
此空項目在陣列定義中既不是 `undefined` 亦不是 `null` ---- 不屬於任何 *Primitives*。在 traverse 時會跳過,在 index 存取時會回傳 `undefined`。
:::
#### String Literals
:::info
**語法糖: Template Tag Function**
*Template Literal* 指的是 \``...`\` 這種 String Literals 表示形式,*Tag Function* 是利用這種語法產生的特別函數表示,一種處理 "String as Input" 的函數更簡潔的寫法。
例:
```javascript=
const processArg = (arg) => {
if (Array.isArray(arg))
return arg.map((item) => `\n arr: ${item}`);
else if (arg.toString === Object.prototype.toString)
return JSON.stringify(arg);
return arg;
};
/*
* segments: string that lies between the arguments.
* For example, `seg[0]...${arg[0]}...seg[1]...${arg[1]}`
*/
const print = (segments, ...args) => {
let result = segments[0];
segments.slice(1).forEach((seg, index) => {
const arg = processArg(args[index]);
result += arg + seg;
});
console.log(result);
};
const arr = [1, 2, 3];
const obj = { a: 0, b: 1 };
print`Template Tag Function:
My Arr: ${arr}
My Obj: ${obj}
`;
/*
* "Template Tag Function:
* My Arr:
* arr: 1,
* arr: 2,
* arr: 3
* My Obj: { "a": 0, "b": 1 }
*/
```
:::
### Symbol
*Primitive Data Type* (ECMAScript 2015)
使用 `Symbol` 來保證 property 唯一性使用,且不會被覆蓋掉。可用於 Object 中 properties 衝突的情況。其用意為提供物件內部方法,由 runtime 機制保護使外部無法存取的一種「封裝」形式。
[Symbol - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)
```javascript=
let s = Symbol("name: s");
let obj = {
[s]: s.toString()
};
console.log(obj[s]); // "Symbol(name: s)"
```
:::info
**Well-Known Symbol**
以 Symbol 來客製化修改 JavaScript 原生語言行為,例如: 定義 Object 的 constructor 中的 `Symbol.hasInstance` 屬性,JavaScript 引擎會在執行時動態使 `instanceof` 語法轉而調用此自定義的函式。
```javascript=
class MyArray {
static [Symbol.hasInstance](instance) {
return Array.isArray(instance) ? "YES" : "NO";
}
}
const arr = [1, 2, 3];
console.log(arr instanceof MyArray); // YES
```
<!-- :warning: -->
:::
### Symbol.iterator / .asyncIterator
在物件的 prototype 中定義的保留靜態變數 `Symbol.iterator`,用以識別、指定特別協定 (這裡即 *Iterable Protocol*)。
只要在 Prototype Chain 中提供任何符合迭代協定的實作方法,便賦予此資料型別「可迭代」的能力。
:::info
**Iterator**
所有 iterable 的資料型態都可以轉成 Iterator 的類別 (e.g., `.values()` 方法),進而以此規範此 *Iterable Protocol* 之定義、實作,和使用 Iterator 的助手函式等。
**例如:** 在 Map 中欲調用 Array's instance 的 `.reduce()` 方法 (即定義於函式建構子的 `Array.prototype`),必須先將 Map 轉換為 Array (`new Array()`) 再行操作。
```javascript=
const map = new Map([
["A", 1000],
["B", 1500],
["C", 2000],
]);
const total = [...map.values()].reduce((a, b) => a + b);
```
但實際上 Iterator 類別本身就有規範自己的 `.reduce()` 方法,所以不用轉換為 Array 也能使用,省去了中間冗於的 Array 物件建構,提升效能:
```javascript=
const total = map.values().reduce((a, b) => a + b);
```
參考:
- [Symbol.iterator - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator)
- [Iterator - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator)
:::
:::info
**Iterable Protocol**
JavaScript 中符合 *Iterable Protocol* 的方法即為: 此無參數傳遞之函式執行後會返回一符合 `IteratorResult` 界面規範之物件,即: 物件中有以 \<next\> 為 key 的函式,其會返回裝載 \<value\> 和 \<done\> 的物件。
當每次迭代時,例如執行 `for...of`, `func(...)` 時,JavaScript runtime 便會嘗試調用該 Object 在 prototype 中指定的 Symbol.iterator 的方法,在每次迭代中呼叫 `.next()`,並將 \<value\> 指派給特定變數。
> 此 *Iterable Protocol* 也可選擇實作 `return()` / `throw()`,用以呼叫函式可提前進行返回清理程序 / 拋出例外並立即停止執行 (Generator 已內建實作)。
參考:
- [Iteration protocols - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol)
- [Spread syntax (...) - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax)
- [for...of - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of)
:::
#### 使用 Generator (function*)
:::warning
**注意:** `GeneratorFunction` 之物件建構式「可」接受參數,但符合 Iterable Protocol 的方法卻「不」接受參數 (e.g., `for...of` 沒有,也不需要可以傳遞 `GeneratorFunction` 構造參數的設計)。
:::
```javascript=
const Generator = function* (...arg) {
// Only pure generator function can pass the arguments.
console.log(arg);
yield 1;
yield 2;
yield 3;
};
const obj = {
[Symbol.iterator]: Generator
};
// or
const obj2 = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
};
// class
class Foo {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (const value of obj); // obj is iterable
for (const value of Generator()); // Generator "itself" is also iterable
[...obj2]; // obj2 is iterable
console.log(...new Foo()); // Foo item is iterable
```
#### 使用自訂符合迭代協定的方法
```javascript=
const iterable_func = function() {
let i = 0;
console.log("Init the iterable function with Closure ...");
return {
next: () => {
const value = `yield: ${i}`;
const done = (i === 10);
i++;
return { value, done };
}
};
}
const obj = {
[Symbol.iterator]: iterable_func
};
for (const value of obj); // obj is iterable
```
### Symbol.asyncIterator
在 Prototype 中定義的保留靜態變數 `Symbol.asyncIterator`,用以識別、指定特別協定 (這裡即 *Async Iterable Protocol*)。
[Symbol.asyncIterator - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator)
:::danger
欲在有 Symbol.asyncIterator 參與的迭代操作中,獲得在主線上阻塞效果,必須搭配使用有提供對應 `await` 方法的實作,如: `for await...of` 而不是 `for...of` / `forEach` (不作用)。
:::
```javascript=
const obj = {
times: [1000, 1000, 1000],
async *[Symbol.asyncIterator]() {
for (const time of this.times) {
await new Promise(r => setTimeout(r, time));
yield `The async iteration await for ${time}`;
}
},
};
(async () => {
for await (const result of obj) {
console.log(result);
}
})();
```
### Generator (function* / async function*)
一種協同式 (Coroutine) 協作多工 (Cooperative Multitasking) 的高階抽象機制,使應用層程式可以自行紀錄處理「當前階段性任務進度」: 可 ==yield== 讓出處理器使用權,並在之後可從工作未完處 ==resume== 恢復進度。
由 JavaScript 語法包裝、runtime 負責此在使用者層級做調度的機制,而不須仰賴作業系統高成本的 context switch 如 *Process* 或 *Thread*。並為符合 *Iterable Protocol* 之一的方法實作 (由其父類別 Iterator 之 prototype 規範,即 `[Symbol.iterator]()`),可用於 iteration 這種階段式漸進完成任務。
> 如此在單執行緒中將多任務交織完成的 Coroutine 機制,也可透過 `async` / `await` 來實現同樣效果,並可與 Generator 互相組合搭配。
:::info
**GeneratorFunction**
JavaScript 沒有任何一個物件實體類別是對應「Generator」,只有 `GeneratorFunction` (Generator 的構造函式) 有確切物件類別歸屬,並繼承自 `Function` 物件。
而「Generator」是經由 `GeneratorFunction() {}.constructor` 產生的實體,因其 `constructor` 也有自己的 prototype,所以「Generator」所具備的正確 prototype 屬性應是 ==GeneratorFunction.prototype.prototype== (常被簡化為 `Generator.prototype`)。
參考:
- [GeneratorFunction - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/GeneratorFunction)
:::
```javascript=
const Generator = function* (arg) {
/*
* Every next() starts/ends at yielding.
*/
console.log(arg); // 1st next() starts
const a = yield 1; // 1st next() stops / 2nd next() starts
/*
* Because the 1st next() "stopped" at yield 1,
* the value of a is assigned at the start of 2nd next(argument)
*/
console.log(a); // 'a'
yield 2; // 2nd next() stops / 3rd next() starts
return 3; // 3nd next() stops
// 4th & the following next() have nothing to run
};
const generator = Generator("Init value");
generator.next(); // 1st next() yields { value: 1, done: false }
generator.next('a'); // 2nd next() yields { value: 2, done: false }
generator.next(); // 3rd next() yields { value: 3, done: true }
generator.next(); // 4th next() yields { value: undefined, done: true }
```
#### 使用 Generator 實作 Coroutines
- **單執行緒上的 Coroutines (function\*)**
[function* - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*)
```javascript=
const Coroutine = function*(name) {
let i = 0;
// Cooperative Coroutine Process
while (true) {
yield `Coroutine: ${name} has processed: ${i++}`;
}
};
const coroutines = [
Coroutine("Stream"),
Coroutine("Read Files"),
];
for(let i = 0; i < 100; i++) {
coroutines.forEach(coroutine => {
// Iterable Protocol
const result = coroutine.next().value;
console.log(result);
});
}
```
- **多執行緒上的 Coroutines (async function\*)**
[async function* - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*)
```javascript=
const AsyncCoroutine = async function*(name, is_await) {
let i = 0;
const sleep = async () => new Promise(r => setTimeout(r, 1000));
// Cooperative Coroutine Process
while (true) {
if (is_await) {
// Coroutine that uses await
await sleep();
const result = `Coroutine: ${name} has processed: ${i++}`;
yield result;
} else {
// Coroutine that returns a Promise
const promise = sleep();
const result = `Coroutine: ${name} has processed: ${i++}`;
yield promise.then(() => result);
}
}
};
```
- **使用 async / await 語法:**
```javascript=
// Coroutines with async/await
const async_coroutines = [
AsyncCoroutine("I/O Multiplexing", true),
AsyncCoroutine("HTTP Request Handling", true),
];
(async () => {
while (true) {
for await (const async_coroutine of async_coroutines) {
// Async Iterable Protocol
const result = (await async_coroutine.next()).value;
console.log(result);
}
}
})();
```
- **使用 Promise 回呼:**
```javascript=
// Coroutines with Promises
const async_coroutines = [
AsyncCoroutine("Async Read Files", false),
AsyncCoroutine("Async Write Files", false),
];
const async_coroutine_handler = (async_coroutine) => {
// Async Iterable Protocol
const result = async_coroutine.next();
result
.then(({ value }) => {
console.log(value);
async_coroutine_handler(async_coroutine);
})
.catch(e => console.error(e));
};
// Init async_coroutines
for (const async_coroutine of async_coroutines) {
async_coroutine_handler(async_coroutine);
}
```
更多:
- [Coroutines in Javascript - Lorenzofox's dev blog](https://lorenzofox.dev/posts/coroutine/)
- [Coroutines and web components - Lorenzofox's dev blog](https://lorenzofox.dev/posts/component-as-infinite-loop/)
### Proxy
類似於「代理模型」的概念,透過多一層的資料結構包裝,作為支持修改物件底層內部方法的手段,可以此更改、擴充 JavaScript 語言預設行為,例如: 擴充對物件操作之記錄、驗證、溝通方法等。
Non-primitive 的物件資料結構中的內部方法有: `getPrototypeOf()`, `setPrototypeOf()`, `isExtensible()`, `preventExtensions()`, `getOwnPropertyDescriptor()`, `defineProperty()`, `has()`, `get()`, `set()`, `deleteProperty()`, `ownKeys()`。
這些都可以透過 Proxy 對物件預設行為進行修改。
[Proxy - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)
#### Handler
欲更改原有的物件內部方法,必須提供相對應的 handler (或叫 *trap*,因其類似於作業系統中 trap 進 kernel 概念,以此方式進行額外作業)。
```javascript=
const target = { a: 1 };
const handler = {
/*
* For example,
* modify the "get" default internal function behavior.
*/
get: (target, name) => {
return `${name}: ${name in target ? target[name] : "default"}`;
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.a, proxy.b); // "a: 1", "b: default"
```
:::warning
**JavaScript 不保證所有有關 Prototype 的操作行為!**
就如這裡 `Proxy` 所示範,任何 `Object` 內部 prototype 的方法都可以透過其來重新定義 ---- 就連原生物件的內部方法也可覆寫。
> 相較原生物件有不同內部方法定義之物件稱作: *Exotic Objects*。
**例:**
```javascript=
const targetArr = [1, 2, 3, 4, 5];
const handler = {
get(target, prop, receiver) {
if (prop === "length") {
return "custom length";
}
return Reflect.get(target, prop, receiver);
}
};
const arr = new Proxy(targetArr, handler);
console.log(Array.isArray(arr)); // true
console.log(arr.length); // "custom length"
```
:::
更多:
- [Proxy pattern - Wiki](https://en.wikipedia.org/wiki/Proxy_pattern)

概念 pseudocode:
```javascript=
class RealSubject {
request() {
// Real subject is doing operations ...
}
}
class Proxy {
constructor(real_subject) {
this.real_subject = real_subject;
}
request() {
// Handler operations before proxying real operations.
...
this.real_subject.request();
// Handler operations after proxying real operations.
...
}
}
const real_subject = new RealSubject();
const proxy = new Proxy(real_subject);
proxy.request();
```
更多:
- Python 中類似機制的 CPython 底層實作: [【python】你知道描述器是多么重要的东西嘛?你写的所有程序都用到了! - 码农高天](https://www.youtube.com/watch?v=Fp7aQO3QuS0)
### Reflect
*"Reflect its own characteristic."*
:::warning
**程式碼、數據自省 (introspection, reflection)**
Runtime 下,可根據場景需要動態反應、改變數據的解釋方式或程式碼的運行邏輯。
:::
同 Proxy 可將 Object 中可變更之內部方法替換效果。最主要用途是在 Proxy 的 handler 中 *forward* 內部預設方法。
[Reflect - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect)
### Prototype
為 Non-Primitives (在 JavaScript 中全以 Object 實作) 複雜資料結構之代表屬性。
:::info
**Prototype Chain**
JavaScript 以 `[[prototype]]` 鍵值代表一物件之 prototype 資料型態,其指向另一個物件資料結構 (而此物件同樣也有自己的 `[[prototype]]` 鍵值,因此連鎖串起了 prototype chain):
```javascript
function Obj() {}
Obj.prototype = { name: "Obj prototype" };
const obj = new Obj();
console.log(obj.__proto__); // Obj.prototype
console.log(obj.__proto__.__proto__); // Object.prototype
```
而在一物件 instance 的 Prototype Chain 上存在任何 prototype 的 properties 資源,都可層遞的被此 instance 存取到 (由下往上游尋找),如:
```javascript=
const parent_prototype = {
public_func() { console.log("Parent"); },
parent_func() { console.log("Parent"); },
};
const child_prototype = {
public_func() { console.log("Child"); },
child_func() { console.log("Child"); },
};
const child = {};
Object.setPrototypeOf(child_prototype, parent_prototype);
Object.setPrototypeOf(child, child_prototype);
child.child_func(); // Child
child.parent_func(); // Parent
child.public_func(); // Child
```
:arrow_right: 關於 prototype chain 對 JavaScript 性能之影響請往下參考「*JavaScript 缺點*」節。
:::
- [Object prototypes - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes)
- [Function: prototype - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/prototype)
- [Object.prototype.constructor - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor)
<!-- :warning: -->
#### \_\_proto\_\_
系統內部對物件 prototype 之實作。
由於 Object 只是一個具多個 key-value pairs 的資料結構,各家 JavaScript 執行環境 (e.g., browsers, node.js) 約定俗成以 `__proto__` 鍵值 (或廣義上的 `[[prototype]]`) 指向該資料結構的父資料結構。
但 `__proto__` 是為供系統內部存取的 prototype 資料型態,理論上僅能由 JavaScript runtime 核心內部存取,開發者若需調用 prototype 建議使用 Constructor 的 `prototype` 鍵值以操作生成物件之 prototype chain,或使用 Object 對 `[[prototype]]` 操作的靜態函式界面。
:::info
**JavaScript's Boxing**
*動態語言特性*
當程式嘗試對 primitives 進行「類物件」操作時,例如:
```javascript
const car = "ford";
console.log(car.length); // 4
```
> 
>
> (Source: [Javascript Boxing and Unboxing - ikbal arslan](https://dev.to/ikbalarslan/javascript-boxing-and-unboxing-4o9g))
怪了,一個 string literal 怎麼會有 properties 呢? 原來,JavaScript runtime 會自動將如此對 primitives 的「類物件」操作以 boxing 包裝,臨時用「代表該屬性的 Object 資料型態」包裹 (e.g., *String*, *Array*, *RegExp*, ...)。所以就算是 primitives 也可存取到其 `[[prototype]]` 和 properties。
:::
但是有了 Boxing 機制,也造就了 JavaScript 奇怪現象:
:::warning
**Primitives 透過自動 Boxing 包裝成 Non-Primitives (Object) 型態**
```javascript
console.log("".__proto__.__proto__ === Object.prototype); // true
```
*primitive 資料型態竟然能擁有 (更客觀的講「可存取」) non-primitive 的 prototype chain!*
這就難怪為什麼許多看似 Object 的 instances 可以直接使用的物件特性都必須使用 Object 靜態函式界面才能存取。因為除非 JavaScript runtime 又每次都對 `Object.prototype` 資源存取進行額外 type 檢查,不然因為 boxing 的特性,使 JavaScript 無法判斷是否是 non-primitive 在操作 `Object.prototype` 啊。
舉例: Object 的 `defineProperty()` 是「Object 靜態方法」而++不能++是「Object instance 方法」(由 `Object.prototype` 定義)。
> **編按:** 動態語言就是這麼的任性難伺候 ...
:::
常見使用範例:
- 使用 「Object 靜態函式界面」以修改該物件之 `[[prototype]]`:
```javascript=
const prototype = {
func() { console.log("prototype"); }
};
const obj = {};
Object.setPrototypeOf(obj, prototype);
Object.getPrototypeOf(obj); // obj.__proto__ = prototype
obj.func(); // "prototype"
```
- 使用「Function Consructor」的 `prototype` 來指定其 instance 的 `[[prototype]]` 以實現 OOP 繼承:
```javascript=
// Parent constructor
function Parent(name) {
console.log("Parent Constructor");
this.name = name;
}
Parent.prototype.func = function() {
console.log(`[Parent] ${this.name}`);
};
// Child constructor
function Child(name) {
Parent.call(this, name);
console.log("Child Constructor");
}
// prototype chains with Parent
Child.prototype = Object.create(Parent.prototype);
// or
Object.assign(Parent.prototype, parent);
// overwrite constructor with Child's instead of Parent's
Child.prototype.constructor = Child;
Child.prototype.func = function() {
console.log(`[Child] ${this.name}`);
};
// new instance of Child
const child = new Child("child");
child.func(); // [Child] child
console.log(child instanceof Child); // true
console.log(child instanceof Parent); // true
```
#### Object.prototype
設定由 Object 所建構的 instance (`new Object()`) 都可以存取的資源。
#### Function.prototype
設定由 Function 所建構的 instance (`new Function()`) 都可以存取的資源。
- **call:** `func.call(execution_context, arg0, arg1, ...)`
- **apply:** `func.apply(execution_context, [arg0, arg1, ...])`
- **bind:** `func.bind(execution_context, arg0, arg1, ...)`
### Heap 資源是否已被回收釋放
可透過「Weak 指標」: 一可指向 Heap 裡尚存的物件,但不新增其 reference mark 的參考指標。來判斷該資源是否可被、已被 JavaScript runtime「動態回收」之依據。
> **注意:** Weak 指標是用來比對「物件變數所指向 Literal 的索引位址」,不是比對「物件變數存在 Stack 的變數位址」!
:::warning
「可」被回收之資源可由 `WeakSet`、`WeakMap` 來判定。
「已」被回收之資源可由 `WeakRef` 或設置 `FinalizationRegistry` 的 callback 來判定。
:::
#### WeakSet
[WeakSet - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakSet)
```javascript=
const weak_set = new WeakSet();
let obj = {};
weak_set.add(obj);
weak_set.has(obj); // true
obj = [1, 2, 3];
weak_set.has(obj); // false (previous obj's reference is lost)
weak_set.add(obj);
weak_set.has(obj); // true
```
#### WeakMap
[WeakMap - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap)
```javascript=
const weak_map = new WeakMap();
let obj = {};
weak_map.set(obj, "obj");
weak_map.get(obj); // "obj"
weak_map.has(obj); // true
obj = [];
weak_map.get(obj); // undefined
weak_map.has(obj); // false
```
#### WeakRef
[WeakRef - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef)
```javascript=
let cache = new WeakRef({ data: "cached data" });
function check_cache(cache) {
let cachedData = cache.deref();
if (cachedData) {
console.log(cachedData.data);
} else {
console.log("Object has been garbage collected");
}
}
check_cache(cache);
```
#### FinalizationRegistry
當 Object 即將被 JavaScript runtime 的 Garbage Collector 回收時,可用此 API 註冊被回收前的回呼函式。
- [FinalizationRegistry - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/FinalizationRegistry)
- [WeakRef and FinalizationRegistry - JavaScript Info](https://javascript.info/weakref-finalizationregistry)
```javascript=
var registry = new FinalizationRegistry((msg) => {
console.log(`GC Callback: ${msg}.`)
})
registry.register(target_obj, "callback_msg");
```
:::info
**註:** Chromium-Based 的瀏覽器可在 Devtool Window > Performance 強制觸發 JavaScript 引擎的資源回收處理程序。

:::
### Object
JavaScript 以 prototype 為基礎來建立階層式的資料結構,以 Object 的 key-value 資料結構實作 prototype 機制 (e.g., 各類別中的 properties (value, methods) 等)。並可串起 Prototype Chain 實現繼承概念。
[Object - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)
#### Properties
:::info
**[JS Engine 實作最佳化] 快速定位 Table 中的 Key Value Pair / Instruction 中內嵌最新一次 Query 資訊**
- **Shapes:** 透過「struct-like 模板」追蹤,從一 Object 之 transition chain 找出對應 key 之 offset。
- **Inline Caching:** 把最新一次操作參數 `obj.a` 的 offset 放在高階 WASM 裡,當下次參數還是 `obj.a` 時便可直接使用。
> 可一併搭配 compile-time 的 *monomorphisation* 實作: [What is monomorphisation with context to C++? - StackOverflow](https://stackoverflow.com/questions/14189604/what-is-monomorphisation-with-context-to-c)
[JavaScript engine fundamentals: Shapes and Inline Caches - Mathias Bynens](https://mathiasbynens.be/notes/shapes-ics)
- Basic property's inner struct:

- Object property inner struct diff transition chain:



- property look-up caching on function calls (like *PLT* ---- dynamic patch):

- array (standard vs. optimiztaion):
- table approach (standard):

- vector approach (optimization):

:::
- [Object.defineProperty()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
```javascript=
const obj = {};
Object.defineProperty(obj, "property1", {
value: 42,
writable: false,
});
obj.property1 = 77; // Won't change & throw an error in strict mode
```
- [Object.getOwnPropertyDescriptor()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)
```javascript=
const obj = {
property1: 42,
};
const descriptor1 = Object.getOwnPropertyDescriptor(obj, "property1");
console.log(descriptor1.configurable); // true
console.log(descriptor1.value); // 42
```
#### Object 重組
- [Object.groupBy()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/groupBy)
將 Object instance 以指定 key 進行分類,並依此回傳「新的」分類物件。
```javascript=
const store = [
{ name: "asparagus", type: "vegetables", count: 5 },
{ name: "bananas", type: "fruit", count: 0 },
{ name: "goat", type: "meat", count: 23 },
{ name: "cherries", type: "fruit", count: 5 },
{ name: "fish", type: "meat", count: 22 },
];
const result = Object.groupBy(store, ({ type }) => type);
/*
* {
* "fruit": [{...}, ...],
* "meat": [{...}, ...],
* }
*/
const result2 = Object.groupBy(store, ({ count }) => count ? "yes" : "no");
/*
* {
* "yes": [{...}, ...],
* "no": [{...}, ...],
* }
*/
```
### Map
Map 為基本資料型別 Object 基礎之上的進階 key-value 資料結構。
:::info
**Map vs. Object**
相較 JavaScript 語言中所有 non-primitives (complex) 資料型態的底層基礎 ---- Object,Map 可視作更高階 key-value pairs,並提供超越 Object 的「Table 資料結構」所需的性質、功能:
- 任何資料型態皆可作為 key/value: primitives 或 non-primitives。
> 對比 Object 僅能以 string 或 symbol 作為其 key。
- 保留 key 安插時的順序: 可用 `for..of` 循序遍歷。
- Map 內建維護 [Map.prototype.size](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/size) 值。
:::
- [Map - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
- [Map vs Object in JavaScript - StackOverflow](https://stackoverflow.com/questions/18541940/map-vs-object-in-javascript)
```javascript=
const map = new Map([
['a', 0],
['b', 1],
]);
map.set('a', 1);
const obj = {};
map.set(obj, 2);
map.get(obj); // 2
map.get({}); // undefined
```
### Set
擁有獨一 value 但沒有 key 索引的集合。並內建有兩兩集合之交集、聯集、差集 ... 方法。
[Set - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set)
```javascript=
const set = new Set([1, 2, 3, 4, 5]);
const set2 = new Set([4, 5, 6]);
set.has(1); // true
set.union(set2); // Set(6) {1, 2, 3, 4, 5, 6}
```
## Web APIs
為實現和統一 Web 技術而訂定的網頁標準 (Web Standard)。
相關資源:
- [Web APIs - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API)
- [IDL Files - Webref - w3c](https://github.com/w3c/webref/tree/main/ed/idl)
> **註:** 但也可以直接從感興趣的系統框架去研究內部 `IDL` 規範,了解實際綁定可供調用的標準 API 有哪些。
### Web Workers
[The structured clone algorithm - Web MDN Docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm)
> The structured clone algorithm copies complex JavaScript objects. It is used internally when invoking `structuredClone()` to transfer data between Workers via `postMessage()`: storing objects with ++IndexedDB++, or copying objects for other APIs.
>
> It clones by recursing through the input object while maintaining a map of previously visited references, to avoid infinitely traversing cycles.
[Transferring objects between threads - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects)
```javascript=
// Create an 8MB "file" and fill it. 8MB = 1024 * 1024 * 8 B
const uInt8Array = new Uint8Array(1024 * 1024 * 8).map((v, i) => i);
console.log(uInt8Array.byteLength); // 8388608
// Transfer the underlying buffer to a worker
worker.postMessage(uInt8Array, [uInt8Array.buffer]);
console.log(uInt8Array.byteLength); // 0
```
## Platform APIs
可與 JavaScript 引擎綁定及互動之底層平台 API 實作。
### Web Browsers
:arrow_right: 詳見: [Anatomy of Web Browser Engines - shibarashinu](https://hackmd.io/@shibarashinu/H1wrAk4o0)
### Electron
使用 JavaScript (Node.js 平台 x V8 引擎驅動) 和 Chromium 前端 (Blink) 搭建的開源跨平台桌面級應用程式框架 (由 Microsoft GitHub 主導開發)。
*VS Code*、*Microsoft Teams*、*Slack*、*Discord*、*Figma*、*Twitch*、*WhatsApp* 等應用都採用 Electron 作為核心。

相關資源:
- [Process Model - Electron](https://www.electronjs.org/docs/latest/tutorial/process-model)
- [Desktop App Development with Electron - Nishant Singh - Medium](https://nshnt.medium.com/desktop-app-development-with-electron-b27ac2b5d4d5)
- [Code Server: Using vscode via Web Browsers - Insu Jang](https://insujang.github.io/2019-11-10/code-server/)
- [VS Code Remote Development - VS Code](https://code.visualstudio.com/docs/remote/remote-overview)

### Node.js
專為 Web 伺服器打造的可執行 JavaScript 服務應用的跨平台後端系統框架,有獨自 Node.js 開發生態和套件管理系統 (npm)。



相關資源:
- [Design overview - Libuv Docs](https://docs.libuv.org/en/v1.x/design.html)
- [An Introduction to libuv - nikhilm](https://nikhilm.github.io/uvbook/index.html)
- [[Series] The Internals of Node.js - Song Cho - Medium](https://bravochos.medium.com/the-internals-of-node-js-9def593c6082)
- [Thread Pool and OS Operations - Aman Dwivedi](https://www.scaler.com/topics/nodejs/thread-pool-and-os-operations/)