跳至主要内容

JS 基礎入門:型別、作用域與呼叫堆疊

涵蓋概念:Primitive Types、Primitives vs Objects、Type Coercion、Equality Operators、Scope and Closures、Call Stack


1. Primitive Types(原始型別)

JavaScript 有 7 種原始型別,可以想成 7 種「寫著內容的紙條」,紙條本身很單純,拿到的就是值本身:

型別說明範例
string文字"你好"
number數字423.14
bigint超大數字123n
boolean真假值truefalse
undefined尚未賦值undefined
null刻意留空null
symbol獨一無二的值Symbol("id")
let name = "小明";
console.log(typeof name); // "string"
console.log(typeof null); // "object" ← 這是 JS 從 1995 年留下來的已知 bug,不會被修正

原始型別的關鍵特性是不可變(Immutable):紙條上的字一旦寫了就不能修改,只能換一張新紙條。


2. Primitives vs Objects(原始型別 vs 物件)

  • 原始型別:一張紙條,複製時複製「內容」,兩份各自獨立
  • 物件:一把置物櫃鑰匙,複製時複製的是「鑰匙」,兩個變數指向同一個櫃子(這叫 call by sharing)
let a = 10;
let b = a;
b = 20;
console.log(a); // 10,完全不受影響

let cat = { name: "小花" };
let sameCat = cat; // 複製的是「鑰匙」
sameCat.name = "小白";
console.log(cat.name); // "小白" ← 也被改了!因為兩者是同一個櫃子

前端實戰地雷(React/Vue 狀態管理):

// 錯誤:以為這樣是複製一份新的
let state = { count: 0 };
let newState = state;
newState.count = 1;
console.log(state.count); // 1,其實根本沒複製到

// 正確:用展開運算子真正複製
let state2 = { count: 0 };
let newState2 = { ...state2 };
newState2.count = 1;
console.log(state2.count); // 0,這次才是獨立的

3. Type Coercion(型別轉換)

JavaScript 會自動幫你把值轉換成需要的型別:

console.log(1 + "2");      // "12"  ← + 遇到字串運算元會轉成字串拼接
console.log("10" - 5); // 5 ← -、*、/ 等算術運算子會把運算元轉成數字
console.log("abc" - 5); // NaN ← 轉不成合法數字就得到 NaN

8 個常見 Falsy Value(一定要背起來)

Boolean(false);      // false
Boolean(0); // false
Boolean(-0); // false
Boolean(0n); // false
Boolean(""); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
// 除了這 8 個,其他一律是 true,包括 "0"、[]、{}
// 例外:document.all 是瀏覽器特例,typeof 是 "undefined" 但實際是物件,且為 falsy

表單驗證地雷:

let inputValue = "0"; // 使用者輸入字串 "0"
if (inputValue) {
console.log("有輸入資料"); // 會印出這行!因為 "0" 是 truthy
}

4. Equality Operators(== vs ===)

  • ==:先自動轉型別再比較(不建議使用)
  • ===:不轉型別,型別不同直接判不相等(業界標準做法
console.log(1 == "1");    // true
console.log(1 === "1"); // false ← 永遠優先使用這個

console.log(NaN === NaN); // false!用 Number.isNaN(NaN) 才是 true
console.log(null == undefined); // true(特例)

Object.is() 修正了 === 的兩個特例:Object.is(NaN, NaN)trueObject.is(0, -0)false


5. Scope and Closures(作用域與閉包)

作用域:全域 → 函式 → 區塊

let globalVar = "院子";
function room() {
let roomVar = "房間"; // 只有這個函式內部看得到
}
if (true) {
let drawerVar = "抽屜"; // 只有這個 { } 區塊內看得到
}
關鍵字作用域可重新賦值建議
var函式作用域,不理會 { }不建議使用
let區塊作用域需要改變值時用
const區塊作用域預設優先使用

閉包:函式會「記住並帶走」外層的變數

function makeCounter() {
let count = 0;
return function () {
count = count + 1;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2 ← 即使 makeCounter 執行完了,count 依然被記住

資料隱私應用:

function createBankAccount() {
let balance = 1000; // 外部完全碰不到
return {
deposit: (amount) => (balance += amount),
getBalance: () => balance
};
}

經典地雷:for 迴圈 + setTimeout

// var:全部共用同一個 i,1 秒後印出 4, 4, 4
for (var i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 1000);
}

// let:每輪迴圈產生全新的 i,印出 1, 2, 3
for (let i = 1; i <= 3; i++) {
setTimeout(() => console.log(i), 1000);
}

6. Call Stack(呼叫堆疊)

JS 用「疊盤子」的方式(LIFO,後進先出)管理函式執行順序:

function first() { console.log("first"); second(); }
function second() { console.log("second"); third(); }
function third() { console.log("third"); }
first();
// 疊上去:[first] → [first, second] → [first, second, third]
// 拿下來:third 先做完 → second → first

報錯的 Stack Trace 就是把當下疊起來的盤子攤開給你看,從最上面開始讀,通常錯誤根源就在那裡。

Stack Overflow(堆疊溢位):

function infiniteLoop() {
infiniteLoop(); // 沒有終止條件
}
infiniteLoop();
// RangeError: Maximum call stack size exceeded

本篇總結

  • 原始型別不可變、物件是共享參照,這個差異貫穿整個 JavaScript
  • === 永遠優先於 ==,並記住 8 個 falsy value
  • const > let > var(優先順序),善用區塊作用域避免 bug
  • 閉包 = 函式記住外層變數,是資料隱私、高階函式的基礎
  • Call Stack 是理解錯誤堆疊、遞迴、非同步機制的起點