JavaScript 基礎
Javascript Engine
- JavaScript 語法是透過 JavaScript Engine 轉換為二進位訊號讓電腦理解
- V8 引擎於 2008 年由 Google 發布,目的是提升 JavaScript 在瀏覽器的效能。Google Map 也得以在 Chrome 瀏覽器順暢運行。V8 發布前後,也有許多引擎問世,其蓬勃發展促使 JavaScript 各方面效能愈好。
Exercise: Javascript Engine
- JavaScript 的主要創造者 Brendan Eich 也開發了最早的 JavaScript 引擎。
在此之前,瀏覽器無法讀取 JavaScript,只能讀取 HTML 與 CSS。
Inside the Engine
- 下列連結可將 JavaScript 代碼轉換為 AST(抽象語法樹 Abstract Syntax Tree)
AST Explorer
Interpreters and Compilers
V8 引擎內建直譯器與編譯器,所以 JavaScript 可以兩種形式執行。
Inside the V8 Engine
- interpreter(直譯器)
直譯,不做任何優化,所以雖然起步快,但整體速度較慢。
例如,若碰上迴圈等重複性質高的程式碼,速度會變慢。 - compiler(編譯器)
總覽後再翻譯,會優化程式碼,所以雖然起步慢,但整體速度較快。
為了解決直譯器、編譯器各自的缺點,JIT Compiler 即時編譯器應運而生,瀏覽器速度大幅提昇。
上圖解說:
- JavaScript 程式碼進入 V8 引擎後,先由 Parser 剖析器轉換為 AST 抽象語法樹。
- 抽象語法樹進入直譯器,逐行轉換為 Bytecode。
- 抽象語法樹進入直譯器後,也會立刻進入 Profiler 及 Compiler,產出 Optimized Code。
- 起步快但速率慢的 Bytecode 與 起步慢但速率快的 Optimized Code 不斷傳遞給電腦,程式得以執行。
JIT 編譯器並非萬能,品質差的程式碼甚至會使編譯器效能降低,導致「反優化」現象。
工程師若能理解以上示意圖的原理,有助於提高編碼品質的意識。
Comparing Other Languages
JavaScript、Java 都是直譯語言,經過各自的直譯器(JavaScript machine or Java virtual machine)直譯成 Bytecode 後可執行,但電腦無法直接執行,因為電腦只懂機械語言,不懂 Bytecode。
C++ 是編譯語言,可直接轉換為機械語言。
問:JavaScript 是直譯語言嗎? 答:視執行環境而定。如果執行環境僅有編譯器,則 JavaScript 就是編譯語言。
Writing Optimized Code
不利於編譯器的語法、導致反優化的語法
- eval( )
- arguments
- for in
- with
- delete
較難理解的概念
- Hidden classes
- Inline caching
結論:編碼順序雜亂、難以預測的程式碼不只令人困惑,也會降低編譯器的效率,導致程式碼反優化。
WebAssembly
問:Why not just use machine code from the beginning?
答:因為機械語言起步速度較慢,會影響使用者體驗。回顧 1995 年,瀏覽器技術不如今日成熟,
發行商又要角逐市佔率,使用者體驗至關重要。今日則有新技術 WA(WebAssembly)
此技術可能在未來改變遊戲規則。
Call Stack and Memory Heap
Memory Heap 堆積記憶體
JavaScript Engine 藉此分配記憶體、儲存變數、程式碼等等Call Stack 堆疊記憶體
JavaScript Engine 藉此追蹤程式碼執行到哪一行
在巢狀結構中,Call Stack 有先進後出的概念。
在瀏覽器中,第一個執行的一定是 Global Execution Context,
所以在其後調用的函式至少會是位於全域執行上下文的巢狀結構內。
第一個進入 Call Stack 的必然是全域執行上下文,
最後一個離開 Call Stack 的也必然是全域執行上下文。
變數的值若是基本型別,就儲存在 Call Stack;若是物件型別就儲存在 Memory Heap。
// Memory Heap
const number = 10; // 將記憶體分配給 number
const string = "some text"; // 將記憶體分配給 string
const human = {
first: "Andrei",
last: "Neagoie",
}; // 將記憶體分配給物件及其值
// Call Stack
function subtractTwo(num) {
return num - 2;
}
function calculate() {
const sumTotal = 4 + 5;
return subtractTwo(sumTotal);
}
debugger;
calculate();
Stack Overflow
以下代碼利用 Recursion 無限次巢狀調用函式,造成 Stack Overflow。
function inception() {
inception();
}
inception();
Memory Leaks
以下是容易導致 Memory Leak 的編碼習慣
// Global Variable:宣告太多全域變數
var a = 1;
var b = 1;
var c = 1;
/*
Event Listeners:宣告事件監聽器,卻不在觸發事件後移除事件監聽器。
倘若使用者頻繁往返同一頁面,事件監聽器將持續增加。
*/
var element = document.getElementById("button");
element.addEventListener("click", onClick);
// setInterval
setInterval(() => {
// referencing objects...
});
Single Threaded
JavaScript 只有一個 Call Stack,是單執行緒語言。
1995 年 JavaScript 誕生之初只需處理單純的 HTML,所以單執行緒就夠用。
單執行緒語言猶如單手進食,只有一隻手能拿食物放入口中,只有口中咀嚼吞嚥食物後才能再放入新的食物。
Exercise: Issue With Single Thread
單執行緒 syncronous 語言的缺點:一次只能執行一件事,
導致整個程式有過多等待時間或中途停滯不前。
alert 語法即是一例,當 alert 視窗彈出,除非按下確定鍵,程式會一直停在 alert。
JavaScript Runtime
JavaScript 單執行緒語言的特性雖然不便,但仍廣受歡迎,
是因為實務上有 Runtime 的配合,使得 JavaScript 有許多彈性。
Web API 由瀏覽器提供,讓 JavaScript 可在 Runtime 之中執行 async 處理。
Runtime 的功能:發送 HTTP 請求、監聽 DOM 事件、setTimeout 等。
在開發者工具的 console 檢視 window 物件,即可檢視 Runtime 提供了什麼功能(Web API);
該功能皆非 JavaScript 原生語法。
瀏覽器係以 C++ 等低階語言提供執行環境及許多語法。
// 試參考上圖解釋程式流程
console.log("1");
setTimeout(() => {
console.log("2"), 0;
});
console.log("3");
- Event Loop 持續觀察 Callback Queue 與 Call Stack 是否閒置
- console.log('1') 進入 Call Stack
- 主控台顯示 1
- console.log('1') 離開 Call Stack
- setTimeout 進入 Call Stack
- JavaScript 將 setTimeout 交給 Web API
- console.log(’3') 進入 Call Stack;Web API 同時處理 setTimeout,
等待零秒後將 setTimeout 丟進 Callback Queue - console.log(’3') 離開 Call Stack;Event Loop 持續詢問 Call Stack 可否接受 console.log(’2’)
- Call Stack 已清空,且程式碼已經執行完最後一行,所以同意 Event Loop 傳遞 console.log(’2’)
- console.log(’2') 進入 Call Stack
- console.log('2') 離開 Call Stack
- 所有程式執行完畢
Node.js
Node.js 是 JavaScript 的執行環境,可實現非同步、非阻塞處理。
JavaScript 原本只能在瀏覽器執行,Node.js 問世後則可在伺服器執行。
Node.js 係以 libuv(C 語言 Library)及 V8 引擎(C++)兩大技術建構。