線上課程觀課進度管理小工具開發日誌
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」。
(Day 46-49 家庭旅遊休假)
變數提升是 JavaScript 在執行前會將宣告「移到作用域頂端」的行為,但不同宣告方式有不同的提升規則: developer.mozilla
var 宣告的變數
只提升宣告,初始化為 undefined
賦值不提升,留在原本位置
function declaration
整個函式都提升(名稱與內容都可用)
可以在宣告前呼叫
function expression
只提升變數名,值為 undefined
函式本體不提升,宣告前呼叫會報錯
// var 提升範例
console.log(a); // undefined(宣告已提升,但賦值未執行)
a(); // TypeError: a is not a function
var a = function() {
console.log('Hello');
};
// function declaration 提升範例
greet(); // "Hi"(整個函式都提升,可以正常呼叫)
function greet() {
console.log('Hi');
}
具名函式表達式中的名稱只在函式內部有效,外部無法直接呼叫: developer.mozilla
const factorial = function calc(n) {
if (n <= 1) return 1;
return n * calc(n - 1); // 內部可用 calc 進行遞迴
};
factorial(5); // 120(外部用 factorial 呼叫)
calc(5); // ReferenceError: calc is not defined
使用情境:
遞迴函式需要呼叫自己
除錯時方便辨識 stack trace
函式內若找不到變數,會沿著作用域鏈往外層尋找,這個機制稱為詞法作用域(Lexical Scope): developer.mozilla
var b = 1;
function outer() {
console.log(b); // undefined(內層 var b 提升,遮蔽外層)
var b = 2; // 內層宣告同名變數,產生遮蔽效果
}
outer();
console.log(b); // 1(全域 b 不受影響)
⚠️ 易錯提醒:
內層宣告的變數會遮蔽外層同名變數(shadowing)
var 的提升行為可能導致非預期的 undefined
特性 | var | let | const |
|---|---|---|---|
重複宣告 | 可以 | 不可 | 不可 |
重新賦值 | 可以 | 可以 | 不可 |
作用域 | function scope | block scope | block scope |
提升行為 | 提升並初始化為 | 提升但不初始化(TDZ) | 提升但不初始化(TDZ) |
宣告前存取 |
| ReferenceError | ReferenceError |
const 固定的是變數與記憶體位址的綁定,而非記憶體內的值: developer.mozilla
const user = { name: 'Alice' };
user.name = 'Bob'; // ✅ 允許,修改物件內容
user = { name: 'Charlie' }; // ❌ TypeError,不能換綁定的物件
const numbers = [1, 2, 3];
numbers.push(4); // ✅ 允許,修改陣列內容
numbers = [5, 6]; // ❌ TypeError,不能換綁定的陣列
記憶法:
const 固定「這條變數線連到哪個盒子」
盒子裡的東西可以換,但不能換盒子
TDZ 是指從區塊開始到 let/const 宣告之間的時期,這段時間內變數無法存取: geeksforgeeks
console.log(typeof x); // "undefined"(未宣告變數)
console.log(typeof y); // ReferenceError(在 TDZ 中)
let y = 10;
TDZ 運作流程: geeksforgeeks
變數宣告被提升但不初始化
從區塊開始到宣告語句前都是 TDZ
在 TDZ 中存取變數會拋出 ReferenceError
執行到宣告語句後,變數才能正常使用
處理複雜規則時,逐步累加比公式計算更直觀且易維護:
// 計算健身房月費(每滿5期折抵200元,首月7.9折)
function calculateTotal(period) {
let total = 0;
let month = 1;
while (month <= period) {
if (month === 1) {
total += 500 * 0.79; // 首月折扣
} else {
total += 500;
if ((month - 1) % 5 === 0) {
total -= 200; // 每滿5期折抵
}
}
month++;
}
return total;
}
適用情境:
規則多變且依賴每期狀態
需要追蹤每期細節(如優惠計算)
日後可能新增特殊月份規則
JavaScript 遞迴有最大呼叫堆疊限制(通常數千層),處理大量資料時建議改用迴圈: youtube
// ❌ 遞迴版本:資料量大時可能 stack overflow
function simulateLoadingRecursive(equipments, maxLoad, current = 0, list = []) {
const randomIndex = Math.floor(Math.random() * equipments.length);
const selected = equipments[randomIndex];
if (current + selected.weight > maxLoad) {
return { equipmentsList: list, currentLoad: current };
}
list.push(selected);
return simulateLoadingRecursive(equipments, maxLoad, current + selected.weight, list);
}
// ✅ 迴圈版本:穩定且效能更好
function simulateLoading(equipments, maxLoad) {
let currentLoad = 0;
const equipmentsList = [];
while (true) {
const randomIndex = Math.floor(Math.random() * equipments.length);
const selected = equipments[randomIndex];
if (currentLoad + selected.weight > maxLoad) break;
equipmentsList.push(selected);
currentLoad += selected.weight;
}
return { equipmentsList, currentLoad };
}
遞迴使用時機: youtube
資料結構天然有樹狀/巢狀關係(如 DOM 遍歷)
問題本質是分治法(divide and conquer)
確保有明確的終止條件且深度可控
toFixed() 會自動四捨五入,若需精確控制小數位數,應使用數學運算:
const num = 1.239;
// ❌ toFixed 會四捨五入
console.log(num.toFixed(2)); // "1.24"
// ✅ 用數學運算精確取位
const secondDecimal = Math.floor(num * 100) % 10; // 3
const thirdDecimal = Math.floor(num * 1000) % 10; // 9
取小數位數技巧:
乘以 10 的 n 次方
用 Math.floor() 取整數部分
對 10 取餘數,得到第 n 位數字
將業務格式資料轉換成計算友善的扁平結構,提升開發效率:
// 原始資料格式(業務規格)
const rawData = [
{
name: 'Kettlebell',
spec: ['4 kg', '6 kg', '8 kg'],
weight: [4, 6, 8]
}
];
// 轉換成計算友善的扁平格式
const flatData = rawData.flatMap(item =>
item.spec.map((spec, index) => ({
name: item.name,
spec,
weight: item.weight[index]
}))
);
// 結果: [
// { name: 'Kettlebell', spec: '4 kg', weight: 4 },
// { name: 'Kettlebell', spec: '6 kg', weight: 6 },
// { name: 'Kettlebell', spec: '8 kg', weight: 8 }
// ]
實務價值:
減少手動前置處理工作
更貼近真實專案的資料流設計
程式碼更易測試與維護
處理字母轉換時,用 UTF-16 編碼查表比 indexOf 更高效:
const A_CODE = 'A'.charCodeAt(0); // 65
const Z_CODE = 'Z'.charCodeAt(0); // 90
function caesarCipher(char, shift) {
const code = char.charCodeAt(0);
if (code >= A_CODE && code <= Z_CODE) {
// 計算偏移後的位置
const offset = (code - A_CODE + shift) % 26;
return String.fromCharCode(A_CODE + offset);
}
return char;
}
caesarCipher('C', 3); // "F"
適用情境:
字母表查表與轉換
加密演算法實作
字母排序與比對
(Day 46-49 家庭旅遊休假)
變數提升是 JavaScript 在執行前會將宣告「移到作用域頂端」的行為,但不同宣告方式有不同的提升規則: developer.mozilla
var 宣告的變數
只提升宣告,初始化為 undefined
賦值不提升,留在原本位置
function declaration
整個函式都提升(名稱與內容都可用)
可以在宣告前呼叫
function expression
只提升變數名,值為 undefined
函式本體不提升,宣告前呼叫會報錯
// var 提升範例
console.log(a); // undefined(宣告已提升,但賦值未執行)
a(); // TypeError: a is not a function
var a = function() {
console.log('Hello');
};
// function declaration 提升範例
greet(); // "Hi"(整個函式都提升,可以正常呼叫)
function greet() {
console.log('Hi');
}
具名函式表達式中的名稱只在函式內部有效,外部無法直接呼叫: developer.mozilla
const factorial = function calc(n) {
if (n <= 1) return 1;
return n * calc(n - 1); // 內部可用 calc 進行遞迴
};
factorial(5); // 120(外部用 factorial 呼叫)
calc(5); // ReferenceError: calc is not defined
使用情境:
遞迴函式需要呼叫自己
除錯時方便辨識 stack trace
函式內若找不到變數,會沿著作用域鏈往外層尋找,這個機制稱為詞法作用域(Lexical Scope): developer.mozilla
var b = 1;
function outer() {
console.log(b); // undefined(內層 var b 提升,遮蔽外層)
var b = 2; // 內層宣告同名變數,產生遮蔽效果
}
outer();
console.log(b); // 1(全域 b 不受影響)
⚠️ 易錯提醒:
內層宣告的變數會遮蔽外層同名變數(shadowing)
var 的提升行為可能導致非預期的 undefined
特性 | var | let | const |
|---|---|---|---|
重複宣告 | 可以 | 不可 | 不可 |
重新賦值 | 可以 | 可以 | 不可 |
作用域 | function scope | block scope | block scope |
提升行為 | 提升並初始化為 | 提升但不初始化(TDZ) | 提升但不初始化(TDZ) |
宣告前存取 |
| ReferenceError | ReferenceError |
const 固定的是變數與記憶體位址的綁定,而非記憶體內的值: developer.mozilla
const user = { name: 'Alice' };
user.name = 'Bob'; // ✅ 允許,修改物件內容
user = { name: 'Charlie' }; // ❌ TypeError,不能換綁定的物件
const numbers = [1, 2, 3];
numbers.push(4); // ✅ 允許,修改陣列內容
numbers = [5, 6]; // ❌ TypeError,不能換綁定的陣列
記憶法:
const 固定「這條變數線連到哪個盒子」
盒子裡的東西可以換,但不能換盒子
TDZ 是指從區塊開始到 let/const 宣告之間的時期,這段時間內變數無法存取: geeksforgeeks
console.log(typeof x); // "undefined"(未宣告變數)
console.log(typeof y); // ReferenceError(在 TDZ 中)
let y = 10;
TDZ 運作流程: geeksforgeeks
變數宣告被提升但不初始化
從區塊開始到宣告語句前都是 TDZ
在 TDZ 中存取變數會拋出 ReferenceError
執行到宣告語句後,變數才能正常使用
處理複雜規則時,逐步累加比公式計算更直觀且易維護:
// 計算健身房月費(每滿5期折抵200元,首月7.9折)
function calculateTotal(period) {
let total = 0;
let month = 1;
while (month <= period) {
if (month === 1) {
total += 500 * 0.79; // 首月折扣
} else {
total += 500;
if ((month - 1) % 5 === 0) {
total -= 200; // 每滿5期折抵
}
}
month++;
}
return total;
}
適用情境:
規則多變且依賴每期狀態
需要追蹤每期細節(如優惠計算)
日後可能新增特殊月份規則
JavaScript 遞迴有最大呼叫堆疊限制(通常數千層),處理大量資料時建議改用迴圈: youtube
// ❌ 遞迴版本:資料量大時可能 stack overflow
function simulateLoadingRecursive(equipments, maxLoad, current = 0, list = []) {
const randomIndex = Math.floor(Math.random() * equipments.length);
const selected = equipments[randomIndex];
if (current + selected.weight > maxLoad) {
return { equipmentsList: list, currentLoad: current };
}
list.push(selected);
return simulateLoadingRecursive(equipments, maxLoad, current + selected.weight, list);
}
// ✅ 迴圈版本:穩定且效能更好
function simulateLoading(equipments, maxLoad) {
let currentLoad = 0;
const equipmentsList = [];
while (true) {
const randomIndex = Math.floor(Math.random() * equipments.length);
const selected = equipments[randomIndex];
if (currentLoad + selected.weight > maxLoad) break;
equipmentsList.push(selected);
currentLoad += selected.weight;
}
return { equipmentsList, currentLoad };
}
遞迴使用時機: youtube
資料結構天然有樹狀/巢狀關係(如 DOM 遍歷)
問題本質是分治法(divide and conquer)
確保有明確的終止條件且深度可控
toFixed() 會自動四捨五入,若需精確控制小數位數,應使用數學運算:
const num = 1.239;
// ❌ toFixed 會四捨五入
console.log(num.toFixed(2)); // "1.24"
// ✅ 用數學運算精確取位
const secondDecimal = Math.floor(num * 100) % 10; // 3
const thirdDecimal = Math.floor(num * 1000) % 10; // 9
取小數位數技巧:
乘以 10 的 n 次方
用 Math.floor() 取整數部分
對 10 取餘數,得到第 n 位數字
將業務格式資料轉換成計算友善的扁平結構,提升開發效率:
// 原始資料格式(業務規格)
const rawData = [
{
name: 'Kettlebell',
spec: ['4 kg', '6 kg', '8 kg'],
weight: [4, 6, 8]
}
];
// 轉換成計算友善的扁平格式
const flatData = rawData.flatMap(item =>
item.spec.map((spec, index) => ({
name: item.name,
spec,
weight: item.weight[index]
}))
);
// 結果: [
// { name: 'Kettlebell', spec: '4 kg', weight: 4 },
// { name: 'Kettlebell', spec: '6 kg', weight: 6 },
// { name: 'Kettlebell', spec: '8 kg', weight: 8 }
// ]
實務價值:
減少手動前置處理工作
更貼近真實專案的資料流設計
程式碼更易測試與維護
處理字母轉換時,用 UTF-16 編碼查表比 indexOf 更高效:
const A_CODE = 'A'.charCodeAt(0); // 65
const Z_CODE = 'Z'.charCodeAt(0); // 90
function caesarCipher(char, shift) {
const code = char.charCodeAt(0);
if (code >= A_CODE && code <= Z_CODE) {
// 計算偏移後的位置
const offset = (code - A_CODE + shift) % 26;
return String.fromCharCode(A_CODE + offset);
}
return char;
}
caesarCipher('C', 3); // "F"
適用情境:
字母表查表與轉換
加密演算法實作
字母排序與比對
Share Dialog
Share Dialog
No comments yet