線上課程觀課進度管理小工具開發日誌
Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)
Day 9:「修煉圈圈 Practice Rings」Web app 開發日誌 - 2025 六角 AI Vibe Coding 體驗營期末大魔王作業
這份筆記旨在記錄 2025 六角 Vibe Coding 體驗營每日作業 Day 21 的期末專案成果,從一個簡單的個人痛點出發,透過與 AI(在我這個情境中是 Perplexity)協作,在一天內完成了一個包含前後端的全端網頁小工具:「修煉圈圈 Practice Rings」。
<100 subscribers
線上課程觀課進度管理小工具開發日誌
Day 6:JavaScript 程式碼執行排序:遞迴函數、Call Stack、Task Queue
Call Stack;遞迴函數與 call stack 的關係;Task Queue(非同步的概念再釐清)
Day 9:「修煉圈圈 Practice Rings」Web app 開發日誌 - 2025 六角 AI Vibe Coding 體驗營期末大魔王作業
這份筆記旨在記錄 2025 六角 Vibe Coding 體驗營每日作業 Day 21 的期末專案成果,從一個簡單的個人痛點出發,透過與 AI(在我這個情境中是 Perplexity)協作,在一天內完成了一個包含前後端的全端網頁小工具:「修煉圈圈 Practice Rings」。
今天主要完成兩塊:一是「單元測試的層級該怎麼切才好讀?」,二是「如何把充滿 for 迴圈的命令式程式碼,優化成乾淨的宣告式邏輯」,並整理 DOM/BOM 與事件流的閱讀筆記。
在使用 Vitest 或 Jest 撰寫測試時,我們常會糾結:到底要寫幾層 describe?是越平展越好,還是層層包裹比較清晰?
核心原則其實很簡單:測試報告是寫給人看的規格書。目標不是為了分類而分類,而是為了讓閱讀報告的人能一眼看出「現在壞掉的是哪個情境」。
區分不同面向
當同一個函式有多種檢查維度時。例如一個字串計算函式:
describe('字串計算器', () => {
describe('計算正確性', () => { /* 驗證加減乘除結果 */ });
describe('輸入邊界檢查', () => { /* 驗證非法輸入、空值 */ });
describe('輸出格式', () => { /* 驗證小數點位數、字串模板 */ });
});
這樣測試報告會呈現:字串計算器 › 輸出格式 › 應保留兩位小數,路徑非常清晰。
區分不同狀態(Context)
當函式行為依賴外部狀態時:
describe('UserService', () => {
describe('使用者已登入', () => { /* 測試權限與資料存取 */ });
describe('使用者未登入', () => { /* 測試轉導與錯誤訊息 */ });
});
隔離 beforeEach 的作用域
這是一個很實用的技巧。如果你只希望某幾組測試共用一個 setup(例如建立一個含有資料的清單),可以透過巢狀 describe 把 beforeEach 鎖在該層級內,避免汙染全域測試環境。
如果你的測試只是針對「同一個邏輯」進行「多組資料窮舉」,請不要再多包一層 describe('多組輸入')。直接使用參數化測試(Parameterized Testing)會更簡潔:
// ✅ 推薦:直接用 test.each
describe('加法運算', () => {
test.each([
[1, 2, 3],
[5, 5, 10],
[-1, 1, 0]
])('%i + %i 應該等於 %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
});
小結判斷標準:
這一群測試講的是「不同的規格/情境」嗎?是 → 包 describe。
這一群測試只是「不同的輸入資料」嗎?是 → 用 test.each。
今天處理了一個經典的資料處理需求:「在指定範圍內找出符合條件的數字(如質數),並計算其衍生的數值(如倍數)」。
一開始為了快速實作,很容易寫出這種充滿 for 迴圈與 if 判斷的程式碼:
// ❌ 充滿細節控制,閱讀時需要腦內編譯
function getRangeData(lower, upper) {
const result = [];
for (let i = lower; i <= upper; i++) {
if (isPrime(i)) {
// 甚至可能在這裡又寫了一層 for loop 找倍數
result.push(i);
}
}
return result;
}
這種寫法的缺點在於我們必須手動管理索引(index)、邊界條件以及暫存陣列(temp array),容易出錯且難以閱讀。
透過 TDD(測試驅動開發)先建立了保護網後,我將上述邏輯重構。首先,抽出一個通用的工具函式來解決「產生範圍」的問題:
// 透過 Array.from 產生連續數列
function createRange(lower, upper) {
const length = upper - lower + 1;
return Array.from({ length }, (_, index) => lower + index);
}
接著,主邏輯就可以像寫文章一樣順暢:
// ✅ 關注「我要什麼」而不是「怎麼做」
function getRangeData(lower, upper) {
return createRange(lower, upper)
.filter(isPrime) // 1. 篩選質數
.map(num => ({ // 2. 轉換格式
prime: num,
multipliers: findMultipliers(num)
}));
}
重構心得:
createRange 的價值:把重複的 for 迴圈邏輯抽離後,主程式碼瞬間乾淨了。
Array Method Chaining:善用 filter 接 map,能讓資料流向一目了然(Input → Filter → Transform → Output)。
TDD 的安全感:因為先寫了邊界測試(例如輸入範圍不存在質數時回傳空陣列),重構過程中我完全不擔心改壞功能。
除了邏輯訓練,今天也重新整理了瀏覽器運作機制的筆記。
前端三要素各司其職:
HTML (結構):負責資料語意。
CSS (樣式):負責視覺呈現。
JavaScript (行為):負責互動邏輯。
原則上,樣式變更應盡量透過 JS 切換 class 來達成,而非直接操作 style 屬性,這樣才能保持 CSS 的可維護性。

瀏覽器的事件傳遞並非單向,而是分為三個階段:
捕捉階段 (Capturing):事件由 window 往下傳遞至目標元素。
目標階段 (Target):事件到達實際點擊的元素。
冒泡階段 (Bubbling):事件由目標元素往上回傳至 window。
實戰技巧:
event.stopPropagation():這是控制事件流的關鍵,可以用來阻止事件繼續往上冒泡或往下捕捉。在處理複雜的巢狀 UI(如 Modal 內的點擊事件)時非常重要。
window vs global:在瀏覽器中,全域變數 var 會掛載在 window 上;但在 Node.js 環境中,全域物件是 global 且行為稍有不同。這是在跨環境開發時需注意的細節。
今天的練習讓我深刻體會到,寫程式除了要把功能做出來,更要注意設計程式碼的結構。無論是測試案例的分組,還是資料處理流程的宣告式寫法,目的都是為了降低未來的維護成本與認知負擔。
今天主要完成兩塊:一是「單元測試的層級該怎麼切才好讀?」,二是「如何把充滿 for 迴圈的命令式程式碼,優化成乾淨的宣告式邏輯」,並整理 DOM/BOM 與事件流的閱讀筆記。
在使用 Vitest 或 Jest 撰寫測試時,我們常會糾結:到底要寫幾層 describe?是越平展越好,還是層層包裹比較清晰?
核心原則其實很簡單:測試報告是寫給人看的規格書。目標不是為了分類而分類,而是為了讓閱讀報告的人能一眼看出「現在壞掉的是哪個情境」。
區分不同面向
當同一個函式有多種檢查維度時。例如一個字串計算函式:
describe('字串計算器', () => {
describe('計算正確性', () => { /* 驗證加減乘除結果 */ });
describe('輸入邊界檢查', () => { /* 驗證非法輸入、空值 */ });
describe('輸出格式', () => { /* 驗證小數點位數、字串模板 */ });
});
這樣測試報告會呈現:字串計算器 › 輸出格式 › 應保留兩位小數,路徑非常清晰。
區分不同狀態(Context)
當函式行為依賴外部狀態時:
describe('UserService', () => {
describe('使用者已登入', () => { /* 測試權限與資料存取 */ });
describe('使用者未登入', () => { /* 測試轉導與錯誤訊息 */ });
});
隔離 beforeEach 的作用域
這是一個很實用的技巧。如果你只希望某幾組測試共用一個 setup(例如建立一個含有資料的清單),可以透過巢狀 describe 把 beforeEach 鎖在該層級內,避免汙染全域測試環境。
如果你的測試只是針對「同一個邏輯」進行「多組資料窮舉」,請不要再多包一層 describe('多組輸入')。直接使用參數化測試(Parameterized Testing)會更簡潔:
// ✅ 推薦:直接用 test.each
describe('加法運算', () => {
test.each([
[1, 2, 3],
[5, 5, 10],
[-1, 1, 0]
])('%i + %i 應該等於 %i', (a, b, expected) => {
expect(add(a, b)).toBe(expected);
});
});
小結判斷標準:
這一群測試講的是「不同的規格/情境」嗎?是 → 包 describe。
這一群測試只是「不同的輸入資料」嗎?是 → 用 test.each。
今天處理了一個經典的資料處理需求:「在指定範圍內找出符合條件的數字(如質數),並計算其衍生的數值(如倍數)」。
一開始為了快速實作,很容易寫出這種充滿 for 迴圈與 if 判斷的程式碼:
// ❌ 充滿細節控制,閱讀時需要腦內編譯
function getRangeData(lower, upper) {
const result = [];
for (let i = lower; i <= upper; i++) {
if (isPrime(i)) {
// 甚至可能在這裡又寫了一層 for loop 找倍數
result.push(i);
}
}
return result;
}
這種寫法的缺點在於我們必須手動管理索引(index)、邊界條件以及暫存陣列(temp array),容易出錯且難以閱讀。
透過 TDD(測試驅動開發)先建立了保護網後,我將上述邏輯重構。首先,抽出一個通用的工具函式來解決「產生範圍」的問題:
// 透過 Array.from 產生連續數列
function createRange(lower, upper) {
const length = upper - lower + 1;
return Array.from({ length }, (_, index) => lower + index);
}
接著,主邏輯就可以像寫文章一樣順暢:
// ✅ 關注「我要什麼」而不是「怎麼做」
function getRangeData(lower, upper) {
return createRange(lower, upper)
.filter(isPrime) // 1. 篩選質數
.map(num => ({ // 2. 轉換格式
prime: num,
multipliers: findMultipliers(num)
}));
}
重構心得:
createRange 的價值:把重複的 for 迴圈邏輯抽離後,主程式碼瞬間乾淨了。
Array Method Chaining:善用 filter 接 map,能讓資料流向一目了然(Input → Filter → Transform → Output)。
TDD 的安全感:因為先寫了邊界測試(例如輸入範圍不存在質數時回傳空陣列),重構過程中我完全不擔心改壞功能。
除了邏輯訓練,今天也重新整理了瀏覽器運作機制的筆記。
前端三要素各司其職:
HTML (結構):負責資料語意。
CSS (樣式):負責視覺呈現。
JavaScript (行為):負責互動邏輯。
原則上,樣式變更應盡量透過 JS 切換 class 來達成,而非直接操作 style 屬性,這樣才能保持 CSS 的可維護性。

瀏覽器的事件傳遞並非單向,而是分為三個階段:
捕捉階段 (Capturing):事件由 window 往下傳遞至目標元素。
目標階段 (Target):事件到達實際點擊的元素。
冒泡階段 (Bubbling):事件由目標元素往上回傳至 window。
實戰技巧:
event.stopPropagation():這是控制事件流的關鍵,可以用來阻止事件繼續往上冒泡或往下捕捉。在處理複雜的巢狀 UI(如 Modal 內的點擊事件)時非常重要。
window vs global:在瀏覽器中,全域變數 var 會掛載在 window 上;但在 Node.js 環境中,全域物件是 global 且行為稍有不同。這是在跨環境開發時需注意的細節。
今天的練習讓我深刻體會到,寫程式除了要把功能做出來,更要注意設計程式碼的結構。無論是測試案例的分組,還是資料處理流程的宣告式寫法,目的都是為了降低未來的維護成本與認知負擔。
Share Dialog
Share Dialog
No comments yet