線上課程觀課進度管理小工具開發日誌
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

影片來源:Alex 宅幹嘛 - 淺談 Javascript 設計模式 #2
工廠函式 (Factory Function) 和抽象工廠 (Abstract Factory Pattern) 是創建型設計模式,核心目的是將「物件建立邏輯」與「物件使用邏輯」分離,常用於實踐 SOLID 原則中的開閉原則 (Open-Closed Principle) 和依賴反轉原則 (Dependency Inversion Principle)。
特性 | 工廠函式 (Factory Function) | 抽象工廠 (Abstract Factory) |
|---|---|---|
定義 | 一個函式負責建立並回傳物件,封裝建立邏輯 | 建立一系列相關物件的工廠集合 |
實作方式 | 純函式,根據參數回傳不同配置的物件 | 先定義工廠介面,再根據條件回傳對應的具體工廠 |
生產對象 | 單一產品類型 | 產品族(一系列相關產品) |
抽象層級 | 較低,抽象物件如何被創建 | 較高,抽象工廠如何被創建 |
使用時機 | 需要建立多個相似物件,且屬性需動態決定 | 需要支援多個平台/主題,每個平台都有一整套相關元件 |
範例情境 | 只生產漢堡 | 生產整套速食套餐(漢堡+飲料+點心) |
工廠函式是一個回傳物件的普通函式,不需要使用 new 關鍵字。
// 步驟 1:純函式建立格式化字串
function getBurgerInfo(bread, meat, price) {
return `${bread} + ${meat} 漢堡, 價格 $${price}`;
}
const info = getBurgerInfo('白麵包', '牛肉', 89);
// "白麵包 + 牛肉 漢堡, 價格 $89"
// 步驟 2:解構參數,接收物件
function getBurgerInfo(burger) {
const { bread, meat, price } = burger;
return `${bread} + ${meat} 漢堡, 價格 $${price}`;
}
const cheeseBurger = {
bread: '白麵包',
meat: '牛肉',
price: 89,
};
getBurgerInfo(cheeseBurger);
// "白麵包 + 牛肉 漢堡, 價格 $89"
// 步驟 3:使用閉包建立工廠函式
function createBurger(bread, meat, price) {
return {
getInfo() {
return `${bread} + ${meat} 漢堡, 價格 $${price}`;
},
};
}
const cheeseBurger = createBurger('白麵包', '牛肉', 89);
const chickenBurger = createBurger('全麥麵包', '雞肉', 79);
console.log(cheeseBurger.getInfo()); // "白麵包 + 牛肉 漢堡, 價格 $89"
console.log(chickenBurger.getInfo()); // "全麥麵包 + 雞肉 漢堡, 價格 $79"
// 基礎漢堡工廠
function createBurger(breadType, meatType, price) {
return {
bread: breadType,
meat: meatType,
price,
getDescription() {
return `${breadType}配${meatType}`;
},
};
}
// 大麥克專屬工廠(封裝固定配方)
function createBigMac() {
return createBurger('白麵包', '雙層牛肉', 119);
}
// 無敵豬肉堡工廠
function createMcPork() {
return createBurger('芝麻麵包', '豬肉排', 59);
}
const bigMac = createBigMac();
const mcPork = createMcPork();
console.log(bigMac.getDescription()); // "白麵包配雙層牛肉"
符合開閉原則:當需要新增「勁辣雞腿堡」時,只需新增 createSpicyChicken() 函式,無需修改既有的 createBurger 邏輯。
抽象工廠用於建立一系列相關的產品物件。
// 產品工廠:只負責建立物件
function createBurger(breadType, meatType) {
return {
type: 'burger',
bread: breadType,
meat: meatType,
};
}
function createDessert(name, calories) {
return {
type: 'dessert',
name,
calories,
};
}
function createDrink(name, size) {
return {
type: 'drink',
name,
size,
};
}
function createMcDonaldsMeal(mealType) {
if (mealType === 'classic') {
const burger = createBurger('白麵包', '牛肉');
const dessert = createDessert('蘋果派', 250);
const drink = createDrink('可口可樂', '中杯');
return {
brand: 'McDonalds',
mealType: 'classic',
burger,
dessert,
drink,
describe() {
return `麥當勞經典套餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
if (mealType === 'healthy') {
const burger = createBurger('全麥麵包', '烤雞胸');
const dessert = createDessert('水果切盤', 80);
const drink = createDrink('無糖茶', '大杯');
return {
brand: 'McDonalds',
mealType: 'healthy',
burger,
dessert,
drink,
describe() {
return `麥當勞健康套餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
return null;
}
function createKFCMeal(mealType) {
if (mealType === 'classic') {
const burger = createBurger('香酥麵包', '炸雞排');
const dessert = createDessert('蛋塔', 200);
const drink = createDrink('百事可樂', '中杯');
return {
brand: 'KFC',
mealType: 'classic',
burger,
dessert,
drink,
describe() {
return `肯德基經典餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
if (mealType === 'spicy') {
const burger = createBurger('辣味麵包', '香辣雞腿');
const dessert = createDessert('霜淇淋', 180);
const drink = createDrink('七喜', '大杯');
return {
brand: 'KFC',
mealType: 'spicy',
burger,
dessert,
drink,
describe() {
return `肯德基辣味餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
return null;
}
function createRestaurantMeal(brand, mealType) {
if (brand === 'McDonalds') {
return createMcDonaldsMeal(mealType);
}
if (brand === 'KFC') {
return createKFCMeal(mealType);
}
// 未來可擴充其他品牌
return null;
}
// 使用範例
const mcClassic = createRestaurantMeal('McDonalds', 'classic');
const kfcSpicy = createRestaurantMeal('KFC', 'spicy');
console.log(mcClassic.describe());
// "麥當勞經典套餐: 牛肉堡 + 蘋果派 + 可口可樂"
console.log(kfcSpicy.describe());
// "肯德基辣味餐: 香辣雞腿堡 + 霜淇淋 + 七喜"
在此架構中:
createRestaurantMeal 是抽象工廠
createMcDonaldsMeal 和 createKFCMeal 是具體工廠
每個具體工廠生產一個產品家族(漢堡 + 甜點 + 飲料)
工廠函式不一定需要閉包,但當需要私有變數時,閉包成為核心機制。
概念 | 定義 | 在工廠函式中的作用 |
|---|---|---|
閉包 (Closure) | 函式與其詞法環境的組合,內層函式可記住外層變數 | 讓工廠函式建立的物件擁有私有狀態 |
工廠函式 (Factory Function) | 負責建立並回傳物件的函式 | 提供統一的物件建立介面 |
立即呼叫函式表達式 (Immediately Invoked Function Expression, IIFE) 常與閉包結合,用於建立私有作用域。
const counter = (function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
})();
console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
counter.decrement();
console.log(counter.value()); // 1
當 IIFE 執行時:
建立執行上下文:JavaScript 引擎建立新的 Execution Context
建立環境記錄:建立 Declarative Environment Record 儲存 privateCounter 和 changeBy
回傳物件:IIFE 回傳包含三個方法的物件
閉包保留參照:三個方法持有對環境記錄的參照,privateCounter 不會被垃圾回收
關鍵特性:
counter 儲存的是 IIFE 回傳的物件(包含三個方法)
privateCounter 存在於 IIFE 建立的詞法環境中
三個方法透過閉包共享同一個 privateCounter
// 使用 IIFE + 閉包封裝模組
const myApp = (function () {
let count = 0; // 私有變數
return {
increment() {
count += 1;
},
getCount() {
return count;
},
};
})();
myApp.increment();
console.log(myApp.getCount()); // 1
console.log(myApp.count); // undefined
const ShoppingCart = (function () {
let items = [];
let total = 0;
function calculateTotal() {
total = items.reduce((sum, item) => sum + item.price, 0);
}
return {
addItem(item) {
items.push(item);
calculateTotal();
console.log(`已加入 ${item.name},總價: ${total}`);
},
getTotal() {
return total;
},
clearCart() {
items = [];
total = 0;
},
};
})();
ShoppingCart.addItem({ name: '漢堡', price: 100 });
ShoppingCart.addItem({ name: '可樂', price: 30 });
console.log(ShoppingCart.getTotal()); // 130
// 問題:使用 var 在迴圈中綁定事件
for (var i = 0; i < 3; i += 1) {
document.getElementById(`btn${i}`).addEventListener('click', function () {
console.log(i); // 全部都會印出 3
});
}
// 解法 1:使用 IIFE
for (var i = 0; i < 3; i += 1) {
(function (index) {
document.getElementById(`btn${index}`).addEventListener('click', function () {
console.log(index); // 正確印出 0, 1, 2
});
})(i);
}
// 解法 2:使用 let(推薦)
for (let i = 0; i < 3; i += 1) {
document.getElementById(`btn${i}`).addEventListener('click', function () {
console.log(i); // 正確印出 0, 1, 2
});
}
function UserProfile({ user }) {
return (
<div>
{(() => {
if (!user) return <p>載入中...</p>;
if (user.isVIP) return <h2>VIP 會員:{user.name}</h2>;
return <p>一般會員:{user.name}</p>;
})()}
</div>
);
}
const dbConnection = (function () {
let pool = null;
let isConnected = false;
function createPool() {
pool = require('pg').Pool({
host: 'localhost',
database: 'mydb',
});
isConnected = true;
}
return {
getConnection() {
if (!isConnected) {
createPool();
}
return pool;
},
close() {
if (pool) {
pool.end();
isConnected = false;
}
},
};
})();
const conn = dbConnection.getConnection();
conn.query('SELECT * FROM users', (err, result) => {
console.log(result.rows);
});
const config = (function () {
const env = process.env.NODE_ENV || 'development';
const secrets = {
apiKey: process.env.API_KEY,
dbPassword: process.env.DB_PASSWORD,
};
return {
isDevelopment() {
return env === 'development';
},
getApiKey() {
return secrets.apiKey;
},
};
})();
if (config.isDevelopment()) {
console.log('開發模式');
}
// 危險:閉包持有大量資料
const createHugeClosures = (function () {
const hugeData = new Array(1000000).fill('x'); // 1MB 資料
return {
getData() {
return hugeData; // 閉包持續持有這 1MB
},
};
})();
// 改善:釋放不需要的資料
const safe = (function () {
let tempData = fetchHugeData();
const result = processData(tempData);
tempData = null; // 釋放記憶體
return {
getResult() {
return result;
},
};
})();
私有方法無法直接測試,建議策略:
只測試公開 API 的行為(黑箱測試)
將複雜邏輯拆成獨立的純函式
// helper.js - 可測試的純函式
export function complexCalculation(input) {
return input * 2 + 1;
}
// module.js
import { complexCalculation } from './helper.js';
const myModule = (function () {
return {
publicAPI(input) {
return complexCalculation(input);
},
};
})();
使用情境 | IIFE + 閉包 | ES6 Module |
|---|---|---|
新專案模組化 | 優先使用 | |
維護舊專案 | - | |
單檔案內封裝 | - | |
立即執行邏輯 | - |
const membershipSystem = (function () {
const members = {};
return {
createMember(name) {
if (members[name]) {
throw new Error('會員已存在');
}
members[name] = { totalPeriods: 0 };
},
addPeriods(name, periods) {
if (!members[name]) {
throw new Error('會員不存在');
}
members[name].totalPeriods += periods;
return this.calculateFee(name);
},
calculateFee(name) {
const { totalPeriods } = members[name];
const basePrice = 500;
let total = totalPeriods * basePrice * 0.79;
const discounts = Math.floor(totalPeriods / 5) * 200;
return total - discounts;
},
};
})();
membershipSystem.createMember('Alex');
membershipSystem.addPeriods('Alex', 3);
membershipSystem.addPeriods('Alex', 2);
console.log(membershipSystem.members); // undefined
class Burger {
constructor(breadType, meatType, price) {
this.bread = breadType;
this.meat = meatType;
this.price = price;
}
getInfo() {
return `${this.bread} + ${this.meat} 漢堡, 價格 $${this.price}`;
}
}
class BurgerFactory {
static createBigMac() {
return new Burger('白麵包', '雙層牛肉', 119);
}
static createMcPork() {
return new Burger('芝麻麵包', '豬肉排', 59);
}
}
const bigMac = BurgerFactory.createBigMac();
console.log(bigMac.getInfo());
與函式版本的差異:
Class 使用 this 存取實例屬性
static 方法無需實例化,直接呼叫類別方法
適合需要繼承關係的大型專案
當呼叫方只認得工廠暴露的 API,而不依賴具體實作時,即達成 DIP。
// 不好:直接依賴具體實作
const burger = new Burger('白麵包', '牛肉', 89);
// 好:依賴工廠抽象
const burger = BurgerFactory.createBigMac();
在測試時可輕鬆替換成 mock 工廠:
// test.js
const mockBurgerFactory = {
createBigMac() {
return { getInfo: () => 'Mock Burger' };
},
};

影片來源:Alex 宅幹嘛 - 淺談 Javascript 設計模式 #2
工廠函式 (Factory Function) 和抽象工廠 (Abstract Factory Pattern) 是創建型設計模式,核心目的是將「物件建立邏輯」與「物件使用邏輯」分離,常用於實踐 SOLID 原則中的開閉原則 (Open-Closed Principle) 和依賴反轉原則 (Dependency Inversion Principle)。
特性 | 工廠函式 (Factory Function) | 抽象工廠 (Abstract Factory) |
|---|---|---|
定義 | 一個函式負責建立並回傳物件,封裝建立邏輯 | 建立一系列相關物件的工廠集合 |
實作方式 | 純函式,根據參數回傳不同配置的物件 | 先定義工廠介面,再根據條件回傳對應的具體工廠 |
生產對象 | 單一產品類型 | 產品族(一系列相關產品) |
抽象層級 | 較低,抽象物件如何被創建 | 較高,抽象工廠如何被創建 |
使用時機 | 需要建立多個相似物件,且屬性需動態決定 | 需要支援多個平台/主題,每個平台都有一整套相關元件 |
範例情境 | 只生產漢堡 | 生產整套速食套餐(漢堡+飲料+點心) |
工廠函式是一個回傳物件的普通函式,不需要使用 new 關鍵字。
// 步驟 1:純函式建立格式化字串
function getBurgerInfo(bread, meat, price) {
return `${bread} + ${meat} 漢堡, 價格 $${price}`;
}
const info = getBurgerInfo('白麵包', '牛肉', 89);
// "白麵包 + 牛肉 漢堡, 價格 $89"
// 步驟 2:解構參數,接收物件
function getBurgerInfo(burger) {
const { bread, meat, price } = burger;
return `${bread} + ${meat} 漢堡, 價格 $${price}`;
}
const cheeseBurger = {
bread: '白麵包',
meat: '牛肉',
price: 89,
};
getBurgerInfo(cheeseBurger);
// "白麵包 + 牛肉 漢堡, 價格 $89"
// 步驟 3:使用閉包建立工廠函式
function createBurger(bread, meat, price) {
return {
getInfo() {
return `${bread} + ${meat} 漢堡, 價格 $${price}`;
},
};
}
const cheeseBurger = createBurger('白麵包', '牛肉', 89);
const chickenBurger = createBurger('全麥麵包', '雞肉', 79);
console.log(cheeseBurger.getInfo()); // "白麵包 + 牛肉 漢堡, 價格 $89"
console.log(chickenBurger.getInfo()); // "全麥麵包 + 雞肉 漢堡, 價格 $79"
// 基礎漢堡工廠
function createBurger(breadType, meatType, price) {
return {
bread: breadType,
meat: meatType,
price,
getDescription() {
return `${breadType}配${meatType}`;
},
};
}
// 大麥克專屬工廠(封裝固定配方)
function createBigMac() {
return createBurger('白麵包', '雙層牛肉', 119);
}
// 無敵豬肉堡工廠
function createMcPork() {
return createBurger('芝麻麵包', '豬肉排', 59);
}
const bigMac = createBigMac();
const mcPork = createMcPork();
console.log(bigMac.getDescription()); // "白麵包配雙層牛肉"
符合開閉原則:當需要新增「勁辣雞腿堡」時,只需新增 createSpicyChicken() 函式,無需修改既有的 createBurger 邏輯。
抽象工廠用於建立一系列相關的產品物件。
// 產品工廠:只負責建立物件
function createBurger(breadType, meatType) {
return {
type: 'burger',
bread: breadType,
meat: meatType,
};
}
function createDessert(name, calories) {
return {
type: 'dessert',
name,
calories,
};
}
function createDrink(name, size) {
return {
type: 'drink',
name,
size,
};
}
function createMcDonaldsMeal(mealType) {
if (mealType === 'classic') {
const burger = createBurger('白麵包', '牛肉');
const dessert = createDessert('蘋果派', 250);
const drink = createDrink('可口可樂', '中杯');
return {
brand: 'McDonalds',
mealType: 'classic',
burger,
dessert,
drink,
describe() {
return `麥當勞經典套餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
if (mealType === 'healthy') {
const burger = createBurger('全麥麵包', '烤雞胸');
const dessert = createDessert('水果切盤', 80);
const drink = createDrink('無糖茶', '大杯');
return {
brand: 'McDonalds',
mealType: 'healthy',
burger,
dessert,
drink,
describe() {
return `麥當勞健康套餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
return null;
}
function createKFCMeal(mealType) {
if (mealType === 'classic') {
const burger = createBurger('香酥麵包', '炸雞排');
const dessert = createDessert('蛋塔', 200);
const drink = createDrink('百事可樂', '中杯');
return {
brand: 'KFC',
mealType: 'classic',
burger,
dessert,
drink,
describe() {
return `肯德基經典餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
if (mealType === 'spicy') {
const burger = createBurger('辣味麵包', '香辣雞腿');
const dessert = createDessert('霜淇淋', 180);
const drink = createDrink('七喜', '大杯');
return {
brand: 'KFC',
mealType: 'spicy',
burger,
dessert,
drink,
describe() {
return `肯德基辣味餐: ${burger.meat}堡 + ${dessert.name} + ${drink.name}`;
},
};
}
return null;
}
function createRestaurantMeal(brand, mealType) {
if (brand === 'McDonalds') {
return createMcDonaldsMeal(mealType);
}
if (brand === 'KFC') {
return createKFCMeal(mealType);
}
// 未來可擴充其他品牌
return null;
}
// 使用範例
const mcClassic = createRestaurantMeal('McDonalds', 'classic');
const kfcSpicy = createRestaurantMeal('KFC', 'spicy');
console.log(mcClassic.describe());
// "麥當勞經典套餐: 牛肉堡 + 蘋果派 + 可口可樂"
console.log(kfcSpicy.describe());
// "肯德基辣味餐: 香辣雞腿堡 + 霜淇淋 + 七喜"
在此架構中:
createRestaurantMeal 是抽象工廠
createMcDonaldsMeal 和 createKFCMeal 是具體工廠
每個具體工廠生產一個產品家族(漢堡 + 甜點 + 飲料)
工廠函式不一定需要閉包,但當需要私有變數時,閉包成為核心機制。
概念 | 定義 | 在工廠函式中的作用 |
|---|---|---|
閉包 (Closure) | 函式與其詞法環境的組合,內層函式可記住外層變數 | 讓工廠函式建立的物件擁有私有狀態 |
工廠函式 (Factory Function) | 負責建立並回傳物件的函式 | 提供統一的物件建立介面 |
立即呼叫函式表達式 (Immediately Invoked Function Expression, IIFE) 常與閉包結合,用於建立私有作用域。
const counter = (function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
})();
console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
counter.decrement();
console.log(counter.value()); // 1
當 IIFE 執行時:
建立執行上下文:JavaScript 引擎建立新的 Execution Context
建立環境記錄:建立 Declarative Environment Record 儲存 privateCounter 和 changeBy
回傳物件:IIFE 回傳包含三個方法的物件
閉包保留參照:三個方法持有對環境記錄的參照,privateCounter 不會被垃圾回收
關鍵特性:
counter 儲存的是 IIFE 回傳的物件(包含三個方法)
privateCounter 存在於 IIFE 建立的詞法環境中
三個方法透過閉包共享同一個 privateCounter
// 使用 IIFE + 閉包封裝模組
const myApp = (function () {
let count = 0; // 私有變數
return {
increment() {
count += 1;
},
getCount() {
return count;
},
};
})();
myApp.increment();
console.log(myApp.getCount()); // 1
console.log(myApp.count); // undefined
const ShoppingCart = (function () {
let items = [];
let total = 0;
function calculateTotal() {
total = items.reduce((sum, item) => sum + item.price, 0);
}
return {
addItem(item) {
items.push(item);
calculateTotal();
console.log(`已加入 ${item.name},總價: ${total}`);
},
getTotal() {
return total;
},
clearCart() {
items = [];
total = 0;
},
};
})();
ShoppingCart.addItem({ name: '漢堡', price: 100 });
ShoppingCart.addItem({ name: '可樂', price: 30 });
console.log(ShoppingCart.getTotal()); // 130
// 問題:使用 var 在迴圈中綁定事件
for (var i = 0; i < 3; i += 1) {
document.getElementById(`btn${i}`).addEventListener('click', function () {
console.log(i); // 全部都會印出 3
});
}
// 解法 1:使用 IIFE
for (var i = 0; i < 3; i += 1) {
(function (index) {
document.getElementById(`btn${index}`).addEventListener('click', function () {
console.log(index); // 正確印出 0, 1, 2
});
})(i);
}
// 解法 2:使用 let(推薦)
for (let i = 0; i < 3; i += 1) {
document.getElementById(`btn${i}`).addEventListener('click', function () {
console.log(i); // 正確印出 0, 1, 2
});
}
function UserProfile({ user }) {
return (
<div>
{(() => {
if (!user) return <p>載入中...</p>;
if (user.isVIP) return <h2>VIP 會員:{user.name}</h2>;
return <p>一般會員:{user.name}</p>;
})()}
</div>
);
}
const dbConnection = (function () {
let pool = null;
let isConnected = false;
function createPool() {
pool = require('pg').Pool({
host: 'localhost',
database: 'mydb',
});
isConnected = true;
}
return {
getConnection() {
if (!isConnected) {
createPool();
}
return pool;
},
close() {
if (pool) {
pool.end();
isConnected = false;
}
},
};
})();
const conn = dbConnection.getConnection();
conn.query('SELECT * FROM users', (err, result) => {
console.log(result.rows);
});
const config = (function () {
const env = process.env.NODE_ENV || 'development';
const secrets = {
apiKey: process.env.API_KEY,
dbPassword: process.env.DB_PASSWORD,
};
return {
isDevelopment() {
return env === 'development';
},
getApiKey() {
return secrets.apiKey;
},
};
})();
if (config.isDevelopment()) {
console.log('開發模式');
}
// 危險:閉包持有大量資料
const createHugeClosures = (function () {
const hugeData = new Array(1000000).fill('x'); // 1MB 資料
return {
getData() {
return hugeData; // 閉包持續持有這 1MB
},
};
})();
// 改善:釋放不需要的資料
const safe = (function () {
let tempData = fetchHugeData();
const result = processData(tempData);
tempData = null; // 釋放記憶體
return {
getResult() {
return result;
},
};
})();
私有方法無法直接測試,建議策略:
只測試公開 API 的行為(黑箱測試)
將複雜邏輯拆成獨立的純函式
// helper.js - 可測試的純函式
export function complexCalculation(input) {
return input * 2 + 1;
}
// module.js
import { complexCalculation } from './helper.js';
const myModule = (function () {
return {
publicAPI(input) {
return complexCalculation(input);
},
};
})();
使用情境 | IIFE + 閉包 | ES6 Module |
|---|---|---|
新專案模組化 | 優先使用 | |
維護舊專案 | - | |
單檔案內封裝 | - | |
立即執行邏輯 | - |
const membershipSystem = (function () {
const members = {};
return {
createMember(name) {
if (members[name]) {
throw new Error('會員已存在');
}
members[name] = { totalPeriods: 0 };
},
addPeriods(name, periods) {
if (!members[name]) {
throw new Error('會員不存在');
}
members[name].totalPeriods += periods;
return this.calculateFee(name);
},
calculateFee(name) {
const { totalPeriods } = members[name];
const basePrice = 500;
let total = totalPeriods * basePrice * 0.79;
const discounts = Math.floor(totalPeriods / 5) * 200;
return total - discounts;
},
};
})();
membershipSystem.createMember('Alex');
membershipSystem.addPeriods('Alex', 3);
membershipSystem.addPeriods('Alex', 2);
console.log(membershipSystem.members); // undefined
class Burger {
constructor(breadType, meatType, price) {
this.bread = breadType;
this.meat = meatType;
this.price = price;
}
getInfo() {
return `${this.bread} + ${this.meat} 漢堡, 價格 $${this.price}`;
}
}
class BurgerFactory {
static createBigMac() {
return new Burger('白麵包', '雙層牛肉', 119);
}
static createMcPork() {
return new Burger('芝麻麵包', '豬肉排', 59);
}
}
const bigMac = BurgerFactory.createBigMac();
console.log(bigMac.getInfo());
與函式版本的差異:
Class 使用 this 存取實例屬性
static 方法無需實例化,直接呼叫類別方法
適合需要繼承關係的大型專案
當呼叫方只認得工廠暴露的 API,而不依賴具體實作時,即達成 DIP。
// 不好:直接依賴具體實作
const burger = new Burger('白麵包', '牛肉', 89);
// 好:依賴工廠抽象
const burger = BurgerFactory.createBigMac();
在測試時可輕鬆替換成 mock 工廠:
// test.js
const mockBurgerFactory = {
createBigMac() {
return { getInfo: () => 'Mock Burger' };
},
};
線上課程觀課進度管理小工具開發日誌
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」。
Share Dialog
Share Dialog
No comments yet