# 我所不知道的 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)。 ::: ![](https://hackmd.io/_uploads/Bk2Bmbsfyl.png) > ~~標題剽竊~~ 標題參考自 [「你所不知道的 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) ![image](https://hackmd.io/_uploads/SJBv5lof1e.png =400x) ::: :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 分析使用。 > ![image](https://hackmd.io/_uploads/HJ10clsfJg.png =500x) > > (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) ![image](https://hackmd.io/_uploads/ryHkiljM1g.png =400x) 概念 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 ``` > ![image](https://hackmd.io/_uploads/S1kbjljMkx.png =300x) > > (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 引擎的資源回收處理程序。 ![image](https://hackmd.io/_uploads/S1GGsloMJg.png =400x) ::: ### 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: ![](https://hackmd.io/_uploads/SyiPGOAVgg.png =550x) - Object property inner struct diff transition chain: ![image](https://hackmd.io/_uploads/Syma__AEll.png =550x) ![image](https://hackmd.io/_uploads/rJagrORVex.png =550x) ![image](https://hackmd.io/_uploads/HkU8SOAVge.png =550x) - property look-up caching on function calls (like *PLT* ---- dynamic patch): ![image](https://hackmd.io/_uploads/BktWadRElx.png =550x) - array (standard vs. optimiztaion): - table approach (standard): ![image](https://hackmd.io/_uploads/H1si6_AVee.png =550x) - vector approach (optimization): ![image](https://hackmd.io/_uploads/r1-Ka_AVge.png =550x) ::: - [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 作為核心。 ![image](https://hackmd.io/_uploads/r1UNoxofJg.png =400x) 相關資源: - [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) ![image](https://hackmd.io/_uploads/r1qQsgjM1g.png =400x) ### Node.js 專為 Web 伺服器打造的可執行 JavaScript 服務應用的跨平台後端系統框架,有獨自 Node.js 開發生態和套件管理系統 (npm)。 ![image](https://hackmd.io/_uploads/B1s_sgiMkl.png =400x) ![image](https://hackmd.io/_uploads/SJgjjxiz1g.png =400x) ![image](https://hackmd.io/_uploads/r1GhsliMJe.png =400x) 相關資源: - [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/)