Joseph Chen
一、學習背景與難點
這份復盤的素材,來自我在 LeetCode 30 Days of JavaScript 學習計畫中, 與 AI 的實際對話紀錄。從最基礎的 Hello World、Counter,到函式組合的 reduceRight, 我把每一個卡關的地方都留了下來。
本次學習覆蓋的題目
Create Hello World Function
Counter
To Be Or Not To Be
Counter II
Filter Elements from Array
Apply Transform Over Each Element
Return Length of Arguments Passed
Function Composition
我在這個學習計畫中遇到的難點,大致可以分成三類:
閉包直覺
知道「閉包會記住外層變數」,但實際遇到 n++ 的回傳值、變數遮蔽,腦袋就打結。
語法誤用
物件方法的 key: value 語法、箭頭函式隱式回傳、rest 參數 ...args,每個都有細節地雷。
函式組合
reduceRight 的執行順序、高階函式為什麼要 return function,腦袋需要建立新的抽象層。
二、核心知識問題點
2-1 閉包(Closure)的本質
閉包最難的不是定義,而是「它記住的是變數本身,不是值」。 每次呼叫內層函式時,讀取的是外層變數的當前狀態,不是建立時的快照。
n++(後置遞增):先回傳 n 的舊值,再將 n 加 1++n(前置遞增):先將 n 加 1,再回傳新值
Counter 題目要求「第一次呼叫回傳 init」,所以必須用 return n++,不能用 return ++n。
2-2 物件方法的 key: value 語法
我在 #2704 To Be Or Not To Be 的時候,完全搞不清楚: 的用途。 後來理解了:在物件字面值(object literal)裡,: 是「鍵值對的分隔符號」,不是 TypeScript 型別標注。
throw new Error("msg") 會中斷執行並拋出例外;return false 只是回傳一個值,函式繼續正常結束。 LeetCode 要求「不相等時拋出例外」,必須用 throw,不能用 return。2-3 Truthy / Falsy:JS 的真假判斷規則
在 #2634 Filter Elements 中,filter 函式的核心就是: 把 fn 回傳值為 truthy 的元素留下來。 JS 的 Falsy 值只有 6 個,其他所有值都是 Truthy。
JS 的 6 個 Falsy 值
false布林假值
0數字零
""空字串
null無值
undefined未定義
NaN非數字
"0"(字串的零)、[](空陣列)、{}(空物件)通通是 Truthy!只有數字 0 是 Falsy。2-4 Rest 參數 ...args
#2703 argumentsLength 讓我第一次認識 rest 參數。...args會把所有傳入的參數收集成一個真正的陣列,可以直接用 .length。
2-5 函式組合與 reduceRight
#2629 Function Composition 是這批題目中概念最難的一題。 函式組合 compose(f, g, h)(x) 的數學定義是 f(g(h(x)))—— 從右到左依序執行。reduceRight 就是專門為這個場景設計的。
return function(x)?因為
compose 本身接收的是「函式陣列」,它必須回傳一個新函式, 等之後傳入 x 才真正執行。這是高階函式的核心模式: 「接收函式、回傳函式」。三、經典例題與程式碼復盤
#2704
To Be Or Not To Be
這題要求實作一個 expect(val) 函式,回傳一個物件, 物件有 toBe 和 notToBe 兩個方法, 分別用 === 嚴格比較,不相等就 throw Error。
expect(5).toBe(5) 等同於先呼叫 expect(5) 拿到物件, 再對這個物件呼叫 .toBe(5)。. 就是在讀取物件的屬性(這裡是個函式)。#2665
Counter II
這題要實作有 increment、decrement、reset 三個方法的計數器。 我在這題犯了一個經典的變數遮蔽(variable shadowing)錯誤。
#2634
Filter Elements from Array
不能用原生 .filter(),要自己從零實作。 我在這題犯了一個傻錯:把 fn 的回傳值(boolean)直接賦值給 newArray, 把陣列蓋掉了。
#2629
Function Composition
最難的一題。除了 reduceRight 的方向要搞清楚, 我還犯了一個打字錯誤:function[i](把陣列名稱打成了關鍵字 function)。
function 是 JS 的保留關鍵字, 如果你把變數名取成 function 會直接語法錯誤。 正確做法是用不衝突的名稱,例如 fn、func、cb(callback 縮寫)。四、容易錯誤點與避坑指南
整理這批題目中反覆出現的坑,每個都附上錯誤寫法 vs 正確寫法的對比。
n++ 後置遞增的回傳值
// 想要「第一次回傳 init,之後每次加 1」
return {
count: () => {
n++; // 先加 1
return n; // 回傳已經加過的 n → 第一次回傳 init+1,答案錯了
}
}// 正確:後置遞增 n++
return {
count: () => {
return n++;
// 等同於:先儲存舊值,回傳舊值,然後 n+1
// 第一次呼叫:回傳 init,n 變成 init+1
}
}n++ 是「先回傳,再遞增」;++n 是「先遞增,再回傳」。Counter 題目要求第一次回傳初始值,所以必須用 n++。
物件方法參數名稱遮蔽閉包變數
var createCounter = function(init) {
let value = init;
return {
// ❌ 參數名叫 value,遮蔽了外層 let value
increment: (value) => {
return ++value; // 修改的是參數 value,不是閉包裡的 value
},
};
};var createCounter = function(init) {
let value = init;
return {
// ✅ 不需要任何參數,直接讀取閉包的 value
increment: () => ++value,
};
};方法不應該接受和閉包變數同名的參數。命名衝突時,內層的參數會遮蔽(shadow)外層的變數,讓閉包失效。
filter 回傳 fn 的結果而非元素本身
for (let i = 0; i < arr.length; i++) {
// ❌ fn() 回傳的是 true/false,不是元素
newArray.push(fn(arr[i], i));
}for (let i = 0; i < arr.length; i++) {
// ✅ fn() 只用來判斷,push 的是 arr[i]
if (fn(arr[i], i)) {
newArray.push(arr[i]);
}
}filter 的語義是「保留元素」,fn 只是「判斷是否保留」的謂詞(predicate)函式。push 的一定是原始元素 arr[i],不是 fn 的回傳值。
== vs === 的嚴格性
// ❌ == 會做型別轉換,結果出乎意料 5 == "5" // true ← 字串被轉成數字 0 == false // true ← false 被轉成 0 null == 0 // false ← 不一致的行為
// ✅ === 嚴格比較,不做型別轉換 5 === "5" // false ← 型別不同,直接 false 0 === false // false ← 型別不同,直接 false null === undefined // false ← 就是不一樣
JavaScript 預設應該始終用 ===(三個等號)。== 的型別轉換規則極其複雜且容易出錯。只有在刻意要利用寬鬆比較(例如 null == undefined)時才考慮 ==。
function 是關鍵字,不能當變數名
// ❌ function 是保留字,這行直接語法錯誤
functions.reduceRight((acc, function) => {
return function(acc);
}, x);// ✅ 用 fn 或 func 等非保留字
functions.reduceRight((acc, fn) => {
return fn(acc);
}, x);JS 的保留關鍵字(function、return、class、const、let 等)不能用作變數名或參數名。一般慣例用 fn 代表「某個函式參數」,cb 代表 callback。
快速複習:本次學到的 JS 特性一覽
n++後置遞增,先回傳舊值,再加 1
閉包(Closure)內層函式記住外層變數的「引用」,不是快照
變數遮蔽內層同名變數/參數會遮蔽外層,閉包操作的是那個被遮住的
Truthy/FalsyFalsy 只有 6 個:false、0、""、null、undefined、NaN
throw vs returnthrow 拋出例外中斷執行,return 正常回傳值
=== vs ==永遠用 ===,== 有型別轉換容易出 bug
...args(rest 參數)收集所有參數成真陣列,可以用所有陣列方法
reduceRight從陣列末端往前累積,函式組合(compose)的標準工具
高階函式接收函式或回傳函式的函式,JS 的核心抽象機制
學習心得
30 Days of JavaScript 的前幾題看起來很簡單,但每一題都藏著一個 JS 的核心觀念。 我犯的每一個錯誤——n++ 回傳值搞混、變數遮蔽、filter push 錯對象、function 關鍵字打字錯誤—— 在真實的前端專案中都會出現,只是那時候可能更難找到。
這份復盤的意義不只是記錄錯誤,而是幫助未來的自己在看到類似程式碼時, 能夠第一眼就認出潛在的問題點。 刷題的價值,在於把這些直覺刻進肌肉記憶裡。
Promise、async/await、Event Loop,這三個主題在非同步 JS 開發中至關重要。