線上課程觀課進度管理小工具開發日誌
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」。
目前練習到的內容,在兩個地方大量用到箭頭函式的形式呼叫方法:
TDD 使用 Vitest 的 test, describe, it => expect 等方法
陣列 map 方法
// 陣列方法的 callback
const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);
// Vitest 基本語法
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
// 測試裡包一層給 toThrow 用
expect(() => divideBy3Times(3.5)).toThrow('n must be a positive integer');
語法糖(syntactic sugar)指的是:語言提供的「更好寫、更好讀」的語法,沒有新增新的能力,只是讓原本就做得到的事情更順手。
如果拿掉語法糖,還是可以用比較囉嗦的原始語法寫出一樣的邏輯。
在沒有用到 this / arguments / new 的情況下,箭頭函式可以直接當成「短版匿名函式」來用,例如:
// 傳統匿名函式
const add = function (a, b) {
return a + b;
};
// 箭頭函式(語法更精簡)
const add = (a, b) => {
return a + b;
};
// 進一步縮寫
const add = (a, b) => a + b;
這些情境裡,箭頭函式的角色:
不會用到 this。
只是讓「宣告一個小小的匿名函式」變得比較短、比較可讀。
可以安全地把它看成「function () { ... } 的縮寫版」。
箭頭函式在 this、arguments、new 等行為上,和傳統函式其實有實質差異,這部分會在之後學到 this、物件方法、建構函式時再深入處理。
目前階段先把箭頭函式當成「短版匿名函式」使用即可;
等學到this時,再回來補「箭頭函式與一般函式在行為上的差異」。
在 Vitest / Jest 這類測試框架中,要驗證「呼叫某個函式會丟出錯誤」時,常看到這種寫法:
const call = () => divideBy3Times(3.5);
expect(call).toThrow('n must be a positive integer');
初學時覺得這個寫法很不直覺,練習 TDD 時跟 happy path 一樣直接代入參數呼叫函式又讓測試因為報錯直接中斷,所以有兩個疑問:
為什麼不能直接寫 expect(divideBy3Times(3.5)).toThrow()?
箭頭函式 () => ... 在這裡到底扮演什麼角色?
expect(divideBy3Times(3.5)).toThrow(); // ❌
執行順序:
JavaScript 會先評估 expect(...) 的參數,這裡是 divideBy3Times(3.5)。
divideBy3Times(3.5) 會立刻被執行。
如果函式內有 throw new Error(...),錯誤會直接往外丟出,整個測試函式在進入 expect 之前就已經中斷。
toThrow() 根本沒機會「幫你檢查」這個錯誤是不是預期中的錯誤。
結果:
測試會因為「未被捕捉的例外」而失敗,而不是因為 toThrow 的判斷失敗。
const call = () => divideBy3Times(3.5);
expect(call).toThrow('n must be a positive integer'); // ✅
執行順序:
call 是一個函式本身,還沒被呼叫。
expect(call) 把「函式本體」當作參數傳進去,而不是傳「呼叫結果」。
toThrow() 的實作會在內部呼叫 call(),並在這個呼叫過程中監聽有沒有錯誤被丟出來。
如果有錯誤丟出來,toThrow 會比對錯誤類型、訊息內容等,決定這個測試要標記為通過或失敗。
關鍵差異:
直接呼叫:錯誤在 expect 外就爆掉了。
包成函式:錯誤被「延後」到 toThrow 內部才被觸發,讓測試框架有機會檢查這個錯誤是不是你預期的。
就語言層面來說,箭頭函式只是其中一種函式表達式(function expression):
// 一般函式表達式
function call () {
return divideBy3Times(3.5);
}
// 箭頭函式寫法
const call = () => divideBy3Times(3.5);
在這個場景下,兩種寫法的「語意角色」是一樣的:
把「呼叫 divideBy3Times(3.5)」這件事包裝成一個可以稍後再執行的函式。
並把這個函式「交給 expect().toThrow()」去呼叫與檢查。
換句話說:
這裡箭頭函式的重點不是「this 綁定」或「語法糖」,而是用來做延遲執行(lazy execution):
不馬上執行 divideBy3Times(3.5)。
先把「要做這件事」改寫成一個函式,讓 toThrow 接管呼叫時機。
想驗證「值」:expect(函式呼叫結果).toBe(...)
想驗證「會不會丟錯」:expect(包起來的函式).toThrow(...)
寫測試時區分:「這次測試要檢查的是回傳值,還是行為(丟錯這件事本身)」,而箭頭函式在這裡就是拿來幫你把「行為」包裝成一個可以交給 expect 的第一級函式。
《圖解資料結構 - 使用 JavaScript》
程式設計、演算法和資料結構的關係
JavaScript 屬於物件導向語言 (OOP),用函式定義屬性和行為
用陣列包陣列表示矩陣,以及用一維矩陣壓縮稀疏矩陣的方法
用一維陣列表達多項式的冪次和係數
《008天重新認識 JavaScript》第三天
瀏覽器上的 JavaScript 包含:
JavaScript 核心 (ECMAScript 標準)
BOM (Browser Object Model)
DOM (Document Object Model)
BOM 控制「瀏覽器本身」:視窗、網址列、導覽相關資訊。 DOM 控制「網頁內容」:節點、文字、屬性、事件等等。

window 是 JavaScript 跟瀏覽器溝通的窗口
DOM tree from w3schools

JavaScript 可透過呼叫文件的屬性、標籤、名稱等方法控制網頁內容
找每次循環除法的商小數點後的特定位數的數值
「在不知道要跑幾次之前,使用 while (true) 這種無限迴圈,配合每一輪的條件判斷決定什麼時候 break;在每一輪裡,把 number 用 toFixed 轉成固定小數位數的字串,再用 indexOf 找出小數點位置,透過字串索引來抽出想要的那一位數字。」
toFixed(2) 是 Number 的方法,回傳「固定小數點後 2 位」的字串。
會做四捨五入,並補零到指定位數。
範例:
10 .toFixed(2) → "10.00"
(3.3333).toFixed(2) → "3.33"
(0.004).toFixed(2) → "0.00"
str.indexOf() 是 String 的方法,,會回傳「指定子字串第一次出現的索引」,如果找不到則回傳 -1。
參考文件來源Number.prototype.toFixed() - JavaScript | MDN
在終端機互動時顯示自訂訊息
import { divideBy3Times } from './q9.js';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const rl = readline.createInterface({ input, output });
async function main () {
const num = await rl.question('請輸入一個正整數:');
const n = parseInt(num, 10);
try {
const times = divideBy3Times(n);
console.log(`${n} 至少要除以 3 連續 ${times} 次,小數點後第二位四捨五入後是零`);
} catch (error) {
console.log('輸入不合法,請輸入一個大於 0 的整數');
} finally {
rl.close();
}
}
main();
parseInt(num, 10) 會把使用者輸入的字串轉成整數或 NaN。
divideBy3Times 內部用 Number.isInteger(n) || n <= 0 檢查輸入是否合法,不合法就 throw new Error(...)。
try...catch 捕捉這個錯誤,用 CLI 顯示友善訊息,而不是讓未捕捉例外把 Node 行程炸掉。
finally 保證 rl.close() 一定會被呼叫,避免 readline 卡住程式。
目前練習到的內容,在兩個地方大量用到箭頭函式的形式呼叫方法:
TDD 使用 Vitest 的 test, describe, it => expect 等方法
陣列 map 方法
// 陣列方法的 callback
const numbers = [1, 2, 3];
const doubled = numbers.map((n) => n * 2);
// Vitest 基本語法
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
// 測試裡包一層給 toThrow 用
expect(() => divideBy3Times(3.5)).toThrow('n must be a positive integer');
語法糖(syntactic sugar)指的是:語言提供的「更好寫、更好讀」的語法,沒有新增新的能力,只是讓原本就做得到的事情更順手。
如果拿掉語法糖,還是可以用比較囉嗦的原始語法寫出一樣的邏輯。
在沒有用到 this / arguments / new 的情況下,箭頭函式可以直接當成「短版匿名函式」來用,例如:
// 傳統匿名函式
const add = function (a, b) {
return a + b;
};
// 箭頭函式(語法更精簡)
const add = (a, b) => {
return a + b;
};
// 進一步縮寫
const add = (a, b) => a + b;
這些情境裡,箭頭函式的角色:
不會用到 this。
只是讓「宣告一個小小的匿名函式」變得比較短、比較可讀。
可以安全地把它看成「function () { ... } 的縮寫版」。
箭頭函式在 this、arguments、new 等行為上,和傳統函式其實有實質差異,這部分會在之後學到 this、物件方法、建構函式時再深入處理。
目前階段先把箭頭函式當成「短版匿名函式」使用即可;
等學到this時,再回來補「箭頭函式與一般函式在行為上的差異」。
在 Vitest / Jest 這類測試框架中,要驗證「呼叫某個函式會丟出錯誤」時,常看到這種寫法:
const call = () => divideBy3Times(3.5);
expect(call).toThrow('n must be a positive integer');
初學時覺得這個寫法很不直覺,練習 TDD 時跟 happy path 一樣直接代入參數呼叫函式又讓測試因為報錯直接中斷,所以有兩個疑問:
為什麼不能直接寫 expect(divideBy3Times(3.5)).toThrow()?
箭頭函式 () => ... 在這裡到底扮演什麼角色?
expect(divideBy3Times(3.5)).toThrow(); // ❌
執行順序:
JavaScript 會先評估 expect(...) 的參數,這裡是 divideBy3Times(3.5)。
divideBy3Times(3.5) 會立刻被執行。
如果函式內有 throw new Error(...),錯誤會直接往外丟出,整個測試函式在進入 expect 之前就已經中斷。
toThrow() 根本沒機會「幫你檢查」這個錯誤是不是預期中的錯誤。
結果:
測試會因為「未被捕捉的例外」而失敗,而不是因為 toThrow 的判斷失敗。
const call = () => divideBy3Times(3.5);
expect(call).toThrow('n must be a positive integer'); // ✅
執行順序:
call 是一個函式本身,還沒被呼叫。
expect(call) 把「函式本體」當作參數傳進去,而不是傳「呼叫結果」。
toThrow() 的實作會在內部呼叫 call(),並在這個呼叫過程中監聽有沒有錯誤被丟出來。
如果有錯誤丟出來,toThrow 會比對錯誤類型、訊息內容等,決定這個測試要標記為通過或失敗。
關鍵差異:
直接呼叫:錯誤在 expect 外就爆掉了。
包成函式:錯誤被「延後」到 toThrow 內部才被觸發,讓測試框架有機會檢查這個錯誤是不是你預期的。
就語言層面來說,箭頭函式只是其中一種函式表達式(function expression):
// 一般函式表達式
function call () {
return divideBy3Times(3.5);
}
// 箭頭函式寫法
const call = () => divideBy3Times(3.5);
在這個場景下,兩種寫法的「語意角色」是一樣的:
把「呼叫 divideBy3Times(3.5)」這件事包裝成一個可以稍後再執行的函式。
並把這個函式「交給 expect().toThrow()」去呼叫與檢查。
換句話說:
這裡箭頭函式的重點不是「this 綁定」或「語法糖」,而是用來做延遲執行(lazy execution):
不馬上執行 divideBy3Times(3.5)。
先把「要做這件事」改寫成一個函式,讓 toThrow 接管呼叫時機。
想驗證「值」:expect(函式呼叫結果).toBe(...)
想驗證「會不會丟錯」:expect(包起來的函式).toThrow(...)
寫測試時區分:「這次測試要檢查的是回傳值,還是行為(丟錯這件事本身)」,而箭頭函式在這裡就是拿來幫你把「行為」包裝成一個可以交給 expect 的第一級函式。
《圖解資料結構 - 使用 JavaScript》
程式設計、演算法和資料結構的關係
JavaScript 屬於物件導向語言 (OOP),用函式定義屬性和行為
用陣列包陣列表示矩陣,以及用一維矩陣壓縮稀疏矩陣的方法
用一維陣列表達多項式的冪次和係數
《008天重新認識 JavaScript》第三天
瀏覽器上的 JavaScript 包含:
JavaScript 核心 (ECMAScript 標準)
BOM (Browser Object Model)
DOM (Document Object Model)
BOM 控制「瀏覽器本身」:視窗、網址列、導覽相關資訊。 DOM 控制「網頁內容」:節點、文字、屬性、事件等等。

window 是 JavaScript 跟瀏覽器溝通的窗口
DOM tree from w3schools

JavaScript 可透過呼叫文件的屬性、標籤、名稱等方法控制網頁內容
找每次循環除法的商小數點後的特定位數的數值
「在不知道要跑幾次之前,使用 while (true) 這種無限迴圈,配合每一輪的條件判斷決定什麼時候 break;在每一輪裡,把 number 用 toFixed 轉成固定小數位數的字串,再用 indexOf 找出小數點位置,透過字串索引來抽出想要的那一位數字。」
toFixed(2) 是 Number 的方法,回傳「固定小數點後 2 位」的字串。
會做四捨五入,並補零到指定位數。
範例:
10 .toFixed(2) → "10.00"
(3.3333).toFixed(2) → "3.33"
(0.004).toFixed(2) → "0.00"
str.indexOf() 是 String 的方法,,會回傳「指定子字串第一次出現的索引」,如果找不到則回傳 -1。
參考文件來源Number.prototype.toFixed() - JavaScript | MDN
在終端機互動時顯示自訂訊息
import { divideBy3Times } from './q9.js';
import readline from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
const rl = readline.createInterface({ input, output });
async function main () {
const num = await rl.question('請輸入一個正整數:');
const n = parseInt(num, 10);
try {
const times = divideBy3Times(n);
console.log(`${n} 至少要除以 3 連續 ${times} 次,小數點後第二位四捨五入後是零`);
} catch (error) {
console.log('輸入不合法,請輸入一個大於 0 的整數');
} finally {
rl.close();
}
}
main();
parseInt(num, 10) 會把使用者輸入的字串轉成整數或 NaN。
divideBy3Times 內部用 Number.isInteger(n) || n <= 0 檢查輸入是否合法,不合法就 throw new Error(...)。
try...catch 捕捉這個錯誤,用 CLI 顯示友善訊息,而不是讓未捕捉例外把 Node 行程炸掉。
finally 保證 rl.close() 一定會被呼叫,避免 readline 卡住程式。
Share Dialog
Share Dialog
No comments yet