iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0

「先讀、再執行」

  • 理解JavaScript的執行流程
    必須先了解「執行環境(Execution Context)」、「作用域(Scope)」與「Hoisting(提升)」。

編譯語言 vs 直譯語言

編譯語言

  • 在開發者寫完程式碼後會「預先編譯」,再執行。
  • 產生獨立的機器碼,可直接與電腦溝通。

直譯語言

  • 執行時透過直譯器動態轉譯成機器碼,再執行。
  • JavaScript 屬於直譯語言,但現代引擎(如 V8)會進行 即時編譯(JIT) 優化。

JavaScript Engine(引擎運作流程)

JavaScript 引擎負責讓瀏覽器或 Node.js 看懂並執行 JS 程式碼。

常見引擎

  • SpiderMonkey(FireFox)
  • V8(Chrome / Node.js)

運作流程

  • 解析(Parsing)
    將原始碼轉成抽象語法樹(AST)。
  • 直譯與執行(Interpreting & Executing)
    直譯器將 AST 轉為機器碼執行。
  • 優化(Optimizing)
    執行後根據實際狀況進行優化;若假設不符,再回退(De-Optimizing)。

執行環境(Execution Context)與堆疊(Call Stack)

任何 JS 程式碼在執行時,都有對應的執行環境。

三種執行環境

  • 全域執行環境(Global Execution Context)

    • 建立全域物件(window / global)
    • 建立 this 並指向全域物件
    • 進行「記憶體指派」階段(Hoisting)
  • 函式執行環境(Functional Execution Context)

    • 每個函式被呼叫時,會建立自己的環境。
    • 會生成 arguments 物件,保存傳入參數。
  • Eval 執行環境

    • 用 eval() 執行字串程式碼,不建議使用(Eval is evil)。

執行堆疊(Call Stack)

JS 採「後進先出(LIFO)」的堆疊結構:

function enterCastle() {
  return floorOne();
}

function floorOne() {
  return floorTwo();
}

function floorTwo() {
  return "任務完成,取得寶藏!";
}

let result = enterCastle();
console.log(result);

函式呼叫順序:

  1. enterCastle()
  2. floorOne()
  3. floorTwo()
  4. 最後依序回傳結果。
  • 像罐裝洋芋片一樣,最先放進去的,最後才能拿出來。

作用域(Scope)

作用域(Scope)又稱「範疇」,代表變數可被存取的範圍

語彙環境(Lexical Environment)

  • 取決於「程式碼寫在哪裡」。
  • JS 採靜態作用域(Lexical Scope),不是動態的。

函式範疇(Function Scope)

  • 由 function 定義的區域。
  • 內部變數無法在外層存取。

區塊範疇(Block Scope)

  • {} 之間的區塊,如 if、for、while。
  • let、const 具有區塊範疇。

作用域鏈(Scope Chain)

  • 若當前範疇找不到變數,會沿著外層鏈逐層尋找。
  • 執行環境決定執行順序,語彙環境決定查找變數順序。

提升(Hoisting)

Hoisting:

  • 在變數或函式宣告之前,即可先被使用,而不會出錯。

JS 執行環境會分成兩個階段:

  • 建立階段(Memory Creation Phase)

    • 找到變數與函式宣告,先在記憶體中保留空間。
    • 變數初始值為 undefined(若用 var 宣告)。
  • 執行階段(Execution Phase)

    • 逐行執行程式碼,指派值與呼叫函式。

一般函式(function 關鍵字宣告)整個內容會被提升,
而變數只提升「宣告」,不提升「賦值」。

stack vs heap

類型 特性
Stack(堆疊) 儲存執行時的函式呼叫與基本型別
Heap(堆積) 儲存物件與動態資料
JS中 變數的宣告與內容會依型別分別存於 Stack 或 Heap

var / let / const 的差異

  • var(function-scoped)
    可重複定義、值可修改。
  • 有 hoisting 行為。
function exampleVar(){
  var x = 1;
  if (true){
    var x = 2; // 同一個變數
    console.log(x); // 2
  }
  console.log(x); // 2
}

Hoisting 範例:

console.log(greeter); // undefined
var greeter = "say hello";

// 實際上等同於:
var greeter;
console.log(greeter);
greeter = "say hello";

let / const(block-scoped)

  • 區塊範疇,不可重複定義。
  • 宣告之前無法使用(會產生 ReferenceError)。
  • 無 var 式 hoisting(但仍在建立階段記錄於記憶體中,稱為「暫時性死區」)。
function exampleLet(){
  let x = 1;
  if (true){
    let x = 2; // 不同變數
    console.log(x); // 2
  }
  console.log(x); // 1
}

function exampleConst(){
  const x = 1;
  if (true){
    const x = 2; // 不同變數
    console.log(x); // 2
  }
  console.log(x); // 1
}

重複宣告會出錯:

function exampleLet(){
  let x = 1;
  let x = 2; // SyntaxError
}

宣告前使用:

console.log(y); // ReferenceError
let y = 5;

3. var vs let 的差異(for loop 範例)
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 3, 3, 3

for (let j = 0; j < 3; j++) {
  setTimeout(() => console.log(j), 100);
}
// 0, 1, 2
  • var 不會為每次迴圈建立新作用域;let 則會。

使用建議

const → 預設選擇
let → 需要修改變數時
var → 避免使用


參考資料


上一篇
Day24|非同步的應用與比較
下一篇
Day26|閉包(Closure)
系列文
程式小白的 30 天轉職挑戰26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言