一個介紹自己基本資料與技術技能的靜態個人自我介紹網頁。
一個包含會員登入、註冊與課程問卷調查的多功能互動式表單網頁。
在 Node.js 環境中執行第一個 JavaScript 程式,輸出個人資訊與招呼語。
// hello.js
console.log("hello, world!");
console.log("金門大學資工系一年級 洪晨祐 111410517");
十題 JavaScript 基礎邏輯與物件操作練習,涵蓋 if-else、迴圈、JSON 解析與物件方法中的 this 關鍵字。
function evaluateScore(score) {
if (score < 0 || score > 100) {
return "無效的分數,分數必須介於 0 到 100 之間。";
}
let grade = "";
let comment = "";
if (score >= 90) {
grade = "A";
comment = "表現優異,繼續保持!";
} else if (score >= 80) {
grade = "B";
comment = "良好表現,還有進步空間。";
} else if (score >= 70) {
grade = "C";
comment = "普通,需要多加練習。";
} else if (score >= 60) {
grade = "D";
comment = "勉強及格,請多努力。";
} else {
grade = "F";
comment = "不及格,請重新複習課程內容。";
}
return "得分: " + score + ", 等第: " + grade + " (" + comment + ")";
}
console.log(evaluateScore(95));
function getFibonacciSeries(n) {
if (n <= 0) return [];
if (n === 1) return [0];
const series = [0, 1];
for (let i = 2; i < n; i++) {
const nextNum = series[i - 1] + series[i - 2];
series.push(nextNum);
}
return series;
}
console.log("前 5 項費氏數列: ", getFibonacciSeries(5));
function calculateSensorAverage(sensorData) {
const validReadings = [];
let index = 0;
while (index < sensorData.length) {
const currentData = sensorData[index];
if (currentData === -999) {
console.log("讀取到結束訊號 -999,停止讀取。");
break;
}
validReadings.push(currentData);
index++;
}
if (validReadings.length === 0) return 0;
let sum = 0;
for (let i = 0; i < validReadings.length; i++) {
sum += validReadings[i];
}
const average = sum / validReadings.length;
console.log("有效讀取次數: " + validReadings.length);
return average;
}
console.log("平均溫度: " + calculateSensorAverage([25.3, 26.1, 24.8, -999, 28.0]));
const hero = {
name: "亞瑟",
level: 1,
hp: 100,
maxHp: 100,
attackPower: 15,
isAlive: true,
takeDamage(damage) {
if (!this.isAlive) {
console.log(this.name + " 已經陣亡,無法再受到傷害。");
return;
}
this.hp -= damage;
console.log(this.name + " 受到 " + damage + " 點傷害!目前生命值: " + this.hp + "/" + this.maxHp);
if (this.hp <= 0) {
this.hp = 0;
this.isAlive = false;
console.log(this.name + " 不幸陣亡了!");
}
},
levelUp() {
if (!this.isAlive) {
console.log(this.name + " 已經陣亡,無法升級。");
return;
}
this.level += 1;
this.maxHp += 20;
this.hp = this.maxHp;
this.attackPower += 5;
console.log(this.name + " 升級了!目前等級: " + this.level + ", 最大生命值: " + this.maxHp);
}
};
hero.takeDamage(30);
hero.levelUp();
const jsonString = '{"catalogName":"科技圖書館","books":[{"id":"B01","title":"JavaScript 入門","author":"林小明","price":390}]}';
const catalog = JSON.parse(jsonString);
console.log("圖書館名稱: " + catalog.catalogName);
const newBook = { id: "B04", title: "HTML5 網頁美學", author: "王大同", price: 350 };
catalog.books.push(newBook);
console.log("序列化 JSON: ", JSON.stringify(catalog));
const tasks = [
{ name: "準備期末報告", completed: false },
{ name: "繳交作業四", completed: true }
];
function showTasksByStatus(completedStatus) {
for (let i = 0; i < tasks.length; i++) {
if (tasks[i].completed === completedStatus) {
console.log("- " + tasks[i].name);
}
}
}
showTasksByStatus(false);
function calculateIncomeTax(annualIncome) {
if (annualIncome <= 0) return 0;
let tax = 0;
if (annualIncome <= 500000) {
tax = annualIncome * 0.05;
} else if (annualIncome <= 1200000) {
tax = 500000 * 0.05 + (annualIncome - 500000) * 0.12;
} else {
tax = 500000 * 0.05 + 700000 * 0.12 + (annualIncome - 1200000) * 0.20;
}
return tax;
}
console.log("應繳所得稅: " + calculateIncomeTax(800000));
const weatherJson = '{"location":"金門","days":["週一","週二","週三"],"temperatures":[28.5, 31.2, 27.0]}';
const data = JSON.parse(weatherJson);
let maxTemp = data.temperatures[0];
for (let i = 1; i < data.temperatures.length; i++) {
if (data.temperatures[i] > maxTemp) maxTemp = data.temperatures[i];
}
console.log("最高溫: " + maxTemp);
const studentGrades = [
{ name: "小明", score: 85 },
{ name: "小華", score: 58 }
];
function groupStudents(students) {
const passed = [];
const failed = [];
for (let i = 0; i < students.length; i++) {
if (students[i].score >= 60) passed.push(students[i].name);
else failed.push(students[i].name);
}
console.log("及格: " + passed.join(", "));
console.log("不及格: " + failed.join(", "));
}
groupStudents(studentGrades);
const inventorySystem = {
storeName: "全家便利店 (大學店)",
items: [
{ name: "御飯糰", price: 35, quantity: 12 },
{ name: "黑咖啡", price: 45, quantity: 20 }
],
calculateTotalValue() {
let totalValue = 0;
for (let i = 0; i < this.items.length; i++) {
totalValue += this.items[i].price * this.items[i].quantity;
}
return totalValue;
},
restockItem(itemName, qty) {
for (let i = 0; i < this.items.length; i++) {
if (this.items[i].name === itemName) {
this.items[i].quantity += qty;
console.log(`[補貨] ${itemName} 成功補貨 ${qty} 個!`);
}
}
}
};
console.log("初始總價值: " + inventorySystem.calculateTotalValue());
inventorySystem.restockItem("黑咖啡", 10);
console.log("更新後總價值: " + inventorySystem.calculateTotalValue());
使用 Node.js + Express + EJS 模板引擎,並搭配 express-session 與權限判定開發的分版本筆記看板管理系統 (Notes App)。
專案演進分版本架構:
| 版本資料夾 | 核心技術要素 | 功能說明 |
|---|---|---|
notes_basic/ | 原生 Express 路由 | 基礎筆記的新增、刪除與列表展示,無使用者概念。 |
notes_auth/ | express-session | 加入登入、註冊機制,用戶需要登入才能建立與刪除自己的筆記。 |
notes_privacy/ | 角色與擁有權驗證 | 支援筆記 Public/Private 設定,私人筆記僅作者本人能查看與編輯。 |
notes_publisher/ | 後端檢索、Header 檔案下載 | 支援關鍵字搜尋過濾,並提供下載個人全部筆記為 JSON 備份功能。 |
Notes Publisher 核心路由邏輯 (app.js 節錄):
// 全文檢索搜尋與權限過濾路由
app.get("/", (req, res) => {
const query = (req.query.q || "").toLowerCase();
const user = req.session.user;
// 篩選出使用者可見的筆記:如果是 Public,或是本人所有的 Private 筆記
let visibleNotes = notes.filter(n => {
return n.public || (user && n.owner === user.username);
});
// 若有搜尋關鍵字,進一步對標題或內容過濾
if (query) {
visibleNotes = visibleNotes.filter(n =>
n.title.toLowerCase().includes(query) ||
n.content.toLowerCase().includes(query)
);
}
res.render("index", { notes: visibleNotes, user, query });
});
// 打包個人筆記並匯出為 JSON 下載
app.get("/export", (req, res) => {
if (!req.session.user) return res.redirect("/login");
const userNotes = notes.filter(n => n.owner === req.session.user.username);
res.setHeader("Content-Disposition", "attachment; filename=my_notes.json");
res.setHeader("Content-Type", "application/json");
res.send(JSON.stringify(userNotes, null, 2));
});
十題 JavaScript 進階函式挑戰,學習 Closure、IIFE、傳址傳參考之根本差異。
function mathTool(num1, num2, action) {
return action(num1, num2);
}
console.log(mathTool(10, 5, (a, b) => a + b)); // 輸出: 15
console.log(mathTool(10, 5, (a, b) => a - b)); // 輸出: 5
(function() {
let count = 100;
console.log("IIFE 內部的 count: " + count);
})();
// 外部嘗試存取 count 會發生 ReferenceError: count is not defined
const prices = [100, 200, 300, 400];
const discounted = prices.map(p => p * 0.8);
console.log("八折後價格:", discounted);
function cleanData(arr) {
arr.pop();
arr.unshift("Start");
}
const dataList = [1, 2, 3];
cleanData(dataList);
console.log("修改後的原始陣列:", dataList);
const multiplier = (factor) => (n) => n * factor;
const double = multiplier(2);
const triple = multiplier(3);
console.log("2 倍計算:", double(10));
console.log("3 倍計算:", triple(10));
function myFilter(arr, callback) {
const result = [];
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
result.push(arr[i]);
}
}
return result;
}
console.log(myFilter([1, 2, 3, 4], x => x > 2));
const users = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 17 },
{ name: "Charlie", age: 20 }
];
const adults = users.filter(user => user.age >= 18);
console.log("成年人列表:", adults);
function process(a, b) {
a.push(99); // 傳址修改 (會影響外部)
b = [100]; // 重新賦值 (指向新記憶體位址,不影響外部)
}
let listA = [1, 2];
let listB = [3, 4];
process(listA, listB);
console.log("listA (傳址修改):", listA);
console.log("listB (重新賦值):", listB);
console.log("任務開始...");
setTimeout(() => {
console.log("延遲 2 秒的非同步任務完成");
}, 2000);
console.log("任務結束,等待非同步中...");
function calculateTotal(cart, discountFunc) {
const total = cart.reduce((sum, price) => sum + price, 0);
return discountFunc(total);
}
const cartPrices = [100, 250, 400];
console.log("折後總價:", calculateTotal(cartPrices, t => t - 100));
十題針對 Express 後端語法設計的實戰練習,探討 error-first callback 錯誤優先與權限驗證。
const config = { port: 8080, dbUrl: "mongodb://localhost" };
console.log("Port (點存取):", config.port);
console.log("DB URL (中括號存取):", config["dbUrl"]);
const req = { body: { username: "yoyo", email: "yoyo@nqu.edu" } };
const { username, email } = req.body;
console.log("解構出的資料:", username, email);
const posts = [{ id: 1, title: "後端架構" }, { id: 2, title: "邊緣計算" }];
posts.forEach(post => {
console.log(`<li>ID: ${post.id}, Title: ${post.title}</li>`);
});
const params = {};
const key = "userId";
params[key] = "111410517";
console.log("動態參數字典:", params);
function fetchData(id, callback) {
if (id <= 0) {
return callback("無效的 ID 參數", null);
}
const fakeData = { id: id, status: "OK" };
callback(null, fakeData);
}
fetchData(1, (err, data) => {
if (err) console.error("發生錯誤: " + err);
else console.log("取得資料成功:", data);
});
function safeParseJSON(jsonStr, callback) {
try {
const obj = JSON.parse(jsonStr);
callback(null, obj);
} catch (err) {
callback(err, null);
}
}
safeParseJSON('{"items":[1,2]}', (err, res) => {
if (err) console.log("解析失敗");
else console.log("解析成功,items:", res.items);
});
function fakeGet(sql, params, callback) {
const fakeRow = { id: 1, title: "後端非同步與錯誤優先" };
if (params[0] !== 1) {
return callback(new Error("SQL 錯誤: 找不到該筆資料 (Record Not Found)"), null);
}
callback(null, fakeRow);
}
fakeGet("SELECT * FROM posts WHERE id = ?", [2], (err, data) => {
if (err) console.log("查詢失敗,如預期拋錯:" + err.message);
else console.log("查詢結果:", data);
});
const userSession = { loggedIn: true, name: "晨祐" };
console.log(`歡迎看板: ${userSession.loggedIn ? `Hello, ${userSession.name}` : "請先登入"}`);
const textList = ["這是一篇非常長長長長長的文章", "短文"];
const processed = textList.map(text => text.length > 10 ? text.slice(0, 10) + "..." : text);
console.log("處理後結果:", processed);
function checkAdmin(role, callback) {
if (role !== "admin") {
callback(new Error("Access Denied"), null);
return;
}
callback(null, "Welcome");
}
checkAdmin("user", (err, msg) => {
if (err) console.log("安全攔截: " + err.message);
else console.log(msg);
});
手寫 Node.js 原生 HTTP 靜態網頁伺服器,設計支援 LocalStorage 與跨分頁同步的幾何包浩斯電商商城。
path.relative 與 BASE_DIR 鋼鐵邊界比對,杜絕路徑遍歷攻擊window.storage 事件,當購物車在任一分頁更新時,其他頁面即時同歩更新期中後端伺服器核心防護代碼 (server.js 節錄):
// 🌟 鋼鐵防線:防止目錄越界路徑遍歷攻擊 (Path Traversal Defense)
const relative = path.relative(BASE_DIR, targetFilePath);
const isOut = relative.startsWith('..') || path.isAbsolute(relative);
if (isOut) {
console.warn(`🚨 [安全警報] 偵測到越界路徑遍歷攻擊! 請求路徑: ${pathname}`);
res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
return res.end("403 Forbidden: 拒絕存取越界路徑。");
}
// 🌟 緩衝管道流分發,避免一次性讀入記憶體,杜絕 OOM 記憶體溢出崩潰
const stream = fs.createReadStream(targetFilePath);
stream.pipe(res);
期中開發問題分析與修正:
將本學期作業 1 到作業 8 整合在同一個 Portal 網頁中。網頁採用極簡幾何排版,以 iframe 嵌入前端作業,並以靜態代碼高亮與模擬終端機輸出呈現控制台作業,確保載入效能與安全性。
網頁設計風格:
整個網頁的風格是用比較簡單乾淨的極簡風(幾何格子的感覺)。網頁使用溫和的灰白色當背景,卡片用純白色,框線全部都是 2px 的黑實線,而且不使用圓角,看起來比較俐落。
作業展示方式:
為了把不同類型的作業整合進同一個網頁,我用了兩種方式來呈現:
<iframe> 標籤把 HTML 網頁內嵌進去,這樣就能直接在 Portal 網頁裡操作。說明與心得:
本期末 Portal 網站的 HTML 排版、CSS 網格佈局與 iframe 安全機制均由 AI(Gemini)輔助生成。藉由這個期末整合,我掌握了多網頁在單一 Portal 頁面動態嵌入與安全性設計,並練習了手動將 JS 控制台作業轉換為高亮程式碼與模擬 Stdout 輸出以提升載入效能的靜態展示技巧。