# 📘 Document
`document` 是 DOM 操作的入口,在純 JavaScript 裡操作 HTML 的核心物件。除了 `querySelector` 與 `createElement`,以下是**最常用也最實用的 `document` 方法與屬性**,我依照用途幫你分類:
---
## 🔍 元素選取(DOM 查詢)
| 方法 | 說明 |
| ----------------------------------- | --------------------------------- |
| `getElementById(id)` | 用 `id` 取得單一元素(老方法,但仍實用) |
| `getElementsByClassName(className)` | 取得有指定 class 的所有元素(HTMLCollection) |
| `getElementsByTagName(tagName)` | 取得指定 tag 的所有元素(HTMLCollection) |
| `querySelector(selector)` | 取得符合 CSS 選擇器的**第一個**元素 |
| `querySelectorAll(selector)` | 取得所有符合的元素(NodeList) |
---
## 🧱 元素建立與節點操作
| 方法/屬性 | 說明 |
| ---------------------------------------------- | ---------------------------------------- |
| `createElement(tag)` | 建立新元素,例如 `document.createElement('div')` |
| `createTextNode(text)` | 建立文字節點(用於插入純文字) |
| `importNode(node, deep)` | 匯入其他 document 的節點 |
| `fragment = document.createDocumentFragment()` | 建立「虛擬 DOM」暫存節點,效率高於直接插入 |
| `document.body`, `document.head` | 直接取 `<body>` 或 `<head>` 節點 |
| `document.documentElement` | 取得 `<html>` 根節點 |
---
## 📄 文件與事件資訊
| 屬性/方法 | 說明 |
| ------------------------------------- | -------------------------------------- |
| `document.title` | 網頁標題,可讀可寫 |
| `document.URL` | 當前網址(唯讀) |
| `document.readyState` | 加載狀態(loading / interactive / complete) |
| `document.addEventListener(type, fn)` | 加事件監聽,像 `DOMContentLoaded` |
| `document.removeEventListener(...)` | 移除事件監聽 |
---
## 📋 複製/剪貼簿(Clipboard)
| 方法 | 說明 |
| ------------------------------------- | ----------------------------- |
| `execCommand('copy')` *(舊)* | 嘗試觸發複製(已過時,建議用 Clipboard API) |
| `navigator.clipboard.readText()` | 讀取剪貼簿內容(async) |
| `navigator.clipboard.writeText(text)` | 將文字寫入剪貼簿 |
---
## 🧪 其它有用方法
| 方法/屬性 | 說明 |
| ----------------------------------------------- | ------------------ |
| `element.contains(child)` | 判斷某元素是否包含另一元素 |
| `element.cloneNode(deep)` | 複製節點(`true`=含子孫) |
| `element.remove()` | 從 DOM 移除元素 |
| `element.append(...)` / `prepend(...)` | 插入元素或文字(可多個) |
| `element.innerHTML`, `textContent`, `outerHTML` | 操作 HTML/純文字/整個元素字串 |
---
## 🧠 小提醒:DOM 節點操作也很多是在 `element` 上
例如:
```js
const div = document.createElement('div');
div.classList.add('box');
div.textContent = 'Hello';
document.body.appendChild(div);
```
這些像 `classList`, `textContent`, `appendChild`, `removeChild` 都是在元素物件上(非 `document` 本身)。
### 使用 `insertAdjacentElement` 將元件串接在後
```js
const btn3 = document.getElementById('myBtn');
const newBtn3 = document.createElement('button');
newBtn3.id = 'newBtn3';
newBtn3.textContent = '新增按鈕 C';
// 直接把 newBtn3 插到 btn3 旁邊(afterend)
btn3.insertAdjacentElement('afterend', newBtn3);
```
---
---
# 📘 Selector
## ✅ `document.querySelectorAll(selector)`
**功能**:
從整個網頁(`document`)中,根據 CSS 選擇器選取「所有符合條件的元素」。
**回傳值**:
一個 `NodeList`(類陣列),可使用 `forEach` 遍歷。
**語法**:
```js
document.querySelectorAll('div.my-class')
```
**範例**:
```html
<div class="my-class">A</div>
<div class="my-class">B</div>
```
```js
const nodes = document.querySelectorAll('div.my-class');
nodes.forEach(el => console.log(el.textContent)); // 依序輸出 A, B
```
---
## ✅ `div.querySelector(selector)`
**功能**:
從指定的 DOM 節點(如某個 `div`)中,查詢**第一個**符合 CSS 選擇器的「子元素」。
**回傳值**:
一個 `Element` 或 `null`
**語法**:
```js
const first = div.querySelector('.inner');
```
**範例**:
```html
<div id="container">
<span class="inner">Hello</span>
<span class="inner">World</span>
</div>
```
```js
const container = document.getElementById('container');
const firstInner = container.querySelector('.inner');
console.log(firstInner.textContent); // Hello(只找第一個)
```
---
## 🆚 差異總覽
| 比較項目 | `document.querySelectorAll` | `div.querySelector` |
| ----- | --------------------------- | ------------------- |
| 查詢範圍 | 整個網頁 | 限定某個元素底下 |
| 回傳類型 | `NodeList`(所有符合) | 第一個符合的元素 or `null` |
| 支援選擇器 | ✅ 支援完整 CSS 選擇器語法 | ✅ 相同 |
| 用途 | 全域查詢 | 區域查詢 |
---
## 👀 延伸補充
| 方法 | 說明 |
| -------------------------------------- | ------------ |
| `document.querySelector('selector')` | 全頁中找第一個符合的元素 |
| `element.querySelectorAll('selector')` | 在某元素下找所有符合的 |
---
## ✅ 常見選擇器語法
以下是常見的 CSS 選擇器類型,均可用於 `querySelector` / `querySelectorAll`:
| 語法 | 說明 | 範例 |
| ---------------- | --------- | ------------------------------------ |
| `tag` | 標籤名稱 | `'div'`、`'input'` |
| `.class` | 類別 | `'.my-class'` |
| `#id` | ID | `'#my-id'` |
| `[attr]` | 屬性存在 | `'[disabled]'`、`'[type]'` |
| `[attr=value]` | 屬性等於值 | `'[type="text"]'`、`'[name="email"]'` |
| `parent child` | 子層級 | `'ul li'`、`'#form input'` |
| `parent > child` | 直接子層級 | `'form > input'` |
| `A + B` | 緊鄰的兄弟元素 | `'label + input'` |
| `A ~ B` | 所有後面的兄弟元素 | `'h2 ~ p'` |
| `:first-child` | 第一個子元素 | `'li:first-child'` |
| `:last-child` | 最後一個子元素 | `'tr:last-child'` |
| `:nth-child(n)` | 第 n 個子元素 | `'li:nth-child(2)'` |
| `:not(selector)` | 排除選擇器 | `'input:not([type="submit"])'` |
---
## ✅ 範例說明
```js
// 取得所有 class 為 "box" 的 div
document.querySelectorAll('div.box');
// 取得 id 為 "main" 底下的第一個 input
document.querySelector('#main input');
// 取得所有 type 為 checkbox 的 input
document.querySelectorAll('input[type="checkbox"]');
// 取得 class 為 .btn 且未 disabled 的按鈕
document.querySelectorAll('button.btn:not([disabled])');
```
---
## ⚠️ 注意事項
1. 傳入的字串必須是 **合法的 CSS 選擇器**,否則會拋出錯誤(SyntaxError)。
2. 如果選擇器中有特殊字元(如空白、點號),記得要用引號包起來。
```js
// 1. 建立一個 <textarea> 元素
var ta = document.createElement('textarea');
// 2. 設定屬性
ta.id = 'myTextarea';
ta.rows = 5;
ta.cols = 40;
ta.placeholder = '在此輸入內容…';
// 3. 新增到 body 底部
document.body.appendChild(ta);
```
---
---
# 📘 JSON
---
## 1. `JSON.stringify` — 將 JavaScript 物件轉成 JSON 字串
* **用途**:把 JS 物件、陣列等,轉成符合 JSON 格式的字串(文字)。
* **用途場景**:要把資料存成字串(例如存在 localStorage、送到後端 API)
### 範例
```js
const obj = { name: "Adam", age: 30, hobbies: ["coding", "music"] };
// 轉成 JSON 字串
const jsonStr = JSON.stringify(obj);
console.log(jsonStr);
// 輸出: '{"name":"Adam","age":30,"hobbies":["coding","music"]}'
console.log(typeof jsonStr); // "string"
```
---
## 2. `JSON.parse` — 將 JSON 字串轉回 JavaScript 物件
* **用途**:把 JSON 格式的字串,轉成可用的 JS 物件或陣列。
* **用途場景**:收到 API 回傳 JSON 字串,或從 localStorage 讀取的字串資料。
### 範例
```js
const jsonStr = '{"name":"Adam","age":30,"hobbies":["coding","music"]}';
// 轉回 JS 物件
const obj = JSON.parse(jsonStr);
console.log(obj.name); // Adam
console.log(obj.hobbies[1]); // music
console.log(typeof obj); // "object"
```
---
## 注意事項
* `JSON.parse` 的輸入必須是**合法的 JSON 字串**,否則會拋錯。
* `JSON.stringify` 只能轉換 JS 可序列化的資料(函式、`undefined`、Symbol 無法轉換)。
---
## 綜合範例
```js
const user = {
id: 1,
name: "Adam",
active: true,
};
// 物件 → JSON 字串
const json = JSON.stringify(user);
// 將 JSON 字串存到 localStorage
localStorage.setItem("user", json);
// 從 localStorage 讀取
const storedJson = localStorage.getItem("user");
// JSON 字串 → 物件
const parsedUser = JSON.parse(storedJson);
console.log(parsedUser.name); // Adam
```
---
---
# 📘 嚴格模式
嚴格模式(Strict Mode)是 JavaScript 在 ES5 以後新增的一種「加強版」執行環境,目的是讓語言更安全、錯誤更早被抓到,也避免一些容易引起混亂的行為。
---
## 嚴格模式簡介
### 1. **開啟方式**
在 JS 檔案或函式最頂端加一行:
```js
"use strict";
```
---
### 2. **主要改變**
* 禁止使用未宣告的變數(避免意外創造全域變數)
* 函式內的 `this` 若沒指定呼叫對象,會是 `undefined`,而不是全域物件 `window`
* 禁止刪除不能刪除的屬性
* 禁止重複參數名稱
* 禁止用 `with` 語句
* 其他讓錯誤更早暴露的限制
---
### 3. **對 Java 使用者的幫助**
* Java 是靜態型別且嚴格的語言,JavaScript 本質比較寬鬆,很容易不小心寫錯不被發現。
* **嚴格模式讓 JS 更嚴謹、更接近 Java 的「錯誤早報」特性**,讓你寫程式時更安心。
* `this` 行為更合理:普通函式沒呼叫對象時 `this` 是 `undefined`,避免你誤用全域物件導致難查錯的 bug。
---
### 4. **簡單範例**
```js
"use strict";
function foo() {
console.log(this);
}
foo(); // undefined(非嚴格模式下是 window)
```
---
---
# 📘 Web Worker 筆記
---
## ✅ 什麼是 Web Worker?
**Web Worker 是一種瀏覽器 API**,可以讓 JavaScript 在主執行緒以外的背景執行緒中執行程式碼,解決 UI 被卡住的問題。
---
### 🧠 背景:JS 是單執行緒的
* 所有 JS 執行都在「主執行緒」(UI Thread)
* 若做大量計算(例如 for-loop、圖片處理、大資料分析),UI 會「卡住」
* Web Worker 讓你把這類運算搬到背景執行,不影響主執行緒
---
## 🧪 範例 1:用外部 `.js` 檔建立 Worker
### 📄 `index.html`
```html
<!DOCTYPE html>
<html>
<body>
<button id="btn">開始運算</button>
<p id="result">尚未執行</p>
<script>
const worker = new Worker("worker.js");
const btn = document.getElementById("btn");
const result = document.getElementById("result");
btn.addEventListener("click", () => {
result.textContent = "背景運算中...";
worker.postMessage("start");
});
worker.onmessage = (e) => {
result.textContent = "結果:" + e.data;
};
</script>
</body>
</html>
```
---
### 📄 `worker.js`
```js
onmessage = function (e) {
if (e.data === "start") {
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
}
postMessage(sum);
}
};
```
---
## 🧪 範例 2:用 `Blob` 建立 Inline Worker(無需額外 `.js` 檔)
```html
<script>
const workerCode = `
self.onmessage = function (e) {
if (e.data === "start") {
let sum = 0;
for (let i = 0; i < 1e9; i++) {
sum += i;
}
self.postMessage(sum);
}
};
`;
const blob = new Blob([workerCode], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
worker.postMessage("start");
worker.onmessage = (e) => console.log("結果:", e.data);
</script>
```
---
## 🔁 主執行緒 ↔ Worker 的溝通方式
| 動作 | API 說明 |
| ------------ | ---------------------------- |
| 發送訊息給 worker | `worker.postMessage(data)` |
| 接收 worker 回覆 | `worker.onmessage = ...` |
| Worker 端接收訊息 | `onmessage = function(e) {}` |
| Worker 回傳主線程 | `postMessage(data)` |
---
## 📌 限制與注意事項
* Worker **無法操作 DOM**
* Worker **不能存取 `window`、`document`、`alert`**
* 可以使用 `fetch`、`setTimeout`、自己定義的函式等
* 與主執行緒間的資料傳遞會經過序列化(建議傳簡單資料或使用 Transferable Object)
---
## ✅ 適用情境
| 任務類型 | 適合用 Worker? | 備註 |
| ------------ | ----------- | ---------------- |
| 長時間 for-loop | ✅ | 可避免 UI 卡頓 |
| 檔案壓縮 / 解壓縮 | ✅ | 類似 zip、json 格式處理 |
| 圖片處理、濾鏡 | ✅ | 不可在主執行緒做太久 |
| 畫面控制、DOM 更新 | ❌ | 不可在 Worker 執行 |
---
## 🧠 與其他技術比較
| 技術 | 執行緒 | 是否能避免 UI 卡住 | 適用於重運算? | 是否能操作 DOM |
| ------------ | -------- | ---------------- | ------- | --------- |
| `setTimeout` | 主執行緒 | ✅ 可稍微緩解 | ❌ 仍占用主緒 | ✅ |
| `await` | 主執行緒(暫停) | ❌ 若是同步 heavy 還是卡 | ❌ | ✅ |
| Web Worker | 背景執行緒 | ✅✅ | ✅✅ | ❌ |
---
## 🧩 Bonus:模組化 Worker(ES Modules 支援)
```js
const worker = new Worker("worker.mjs", { type: "module" });
```
* 支援 `import/export` 語法
* 較新瀏覽器才支援
---
## ✅ 小結筆記
* Web Worker 是瀏覽器提供的真正背景執行環境
* 適用於**長時間、重運算邏輯**
* 無法操作 DOM,僅能與主執行緒訊息傳遞
* 可以透過 `Blob` 內建 Worker,不需額外檔案
---
## ✅ `runWorkerTask` — 通用封裝
### ✅ 功能特性:
* 可 `await`
* 支援 `timeout`(防止 worker 沉默)
* 支援 `error` 捕捉
* 支援一次性通信(worker 結果回來就 resolve)
---
## 📄 用法示範:
```js
const worker = new Worker("worker.js");
try {
const result = await runWorkerTask(worker, { action: "start" }, { timeout: 5000 });
console.log("結果:", result);
} catch (err) {
console.error("Worker 錯誤:", err.message);
}
```
---
## 🧠 封裝函式內容:
```js
function runWorkerTask(worker, message, options = {}) {
const { timeout = 10000 } = options;
return new Promise((resolve, reject) => {
let timeoutId;
// 成功事件
const handleMessage = (e) => {
clearTimeout(timeoutId);
cleanup();
resolve(e.data);
};
// 錯誤事件
const handleError = (e) => {
clearTimeout(timeoutId);
cleanup();
reject(new Error(`Worker error: ${e.message}`));
};
// 清除事件監聽器
const cleanup = () => {
worker.removeEventListener("message", handleMessage);
worker.removeEventListener("error", handleError);
};
// 加入事件監聽器
worker.addEventListener("message", handleMessage);
worker.addEventListener("error", handleError);
// 設定逾時處理
timeoutId = setTimeout(() => {
cleanup();
reject(new Error("Worker timeout"));
}, timeout);
// 發送訊息
worker.postMessage(message);
});
}
```
---
## 🔧 `worker.js`(簡單範例)
```js
self.onmessage = (e) => {
if (e.data.action === "start") {
let sum = 0;
for (let i = 0; i < 1e8; i++) sum += i;
self.postMessage({ result: sum });
}
};
```
---
## 🧠 額外細節說明
| 功能 | 說明 |
| --------------------- | ---------------------------------------------- |
| `removeEventListener` | 避免 memory leak,保證只對一次通信負責 |
| `timeout` | 避免 worker 無限沒回應卡住 Promise |
| `onerror` | 捕捉 syntax error 或 runtime error |
| 傳入 `message` 格式 | 自定義,可用 `{ action: "...", payload: ... }` 模式更清晰 |
---
## 🧠 進階建議(可選)
* 若要支援多次請求同一個 worker,可加 `requestId` 區分(模擬 RPC)
* 若要支援「工作取消」,可擴充 abort 機制(搭配 `AbortController`)
---
## ✅ 小結:你現在擁有的封裝
* 支援 `await`
* 支援逾時
* 安全清除事件監聽
* 可傳任意訊息
* 可應用於所有單次 worker request/response 場景
# 取得所有組合
```js
/**
* 【主函式】取得陣列中所有 C(n, k) 的組合結果
* * @param {Array} arr - 原始資料陣列 (例如:['A', 'B', 'C', 'D', 'E'])
* @param {number} k - 每組要選取的元素數量 (例如:3)
* @returns {Array[]} - 包含所有組合結果的二維陣列
*/
function findCombinations(arr, k) {
// 用來存放最終所有合格組合的「筆記本」
const results = [];
/**
* 【核心遞迴函式】回溯演算法 (Backtracking)
* @param {number} start - 這次搜尋要從原始陣列的哪個索引開始「往後看」
* @param {Array} currentCombo - 目前我手裡已經拿了哪些元素
*/
function backtrack(start, currentCombo) {
// --- 終止條件 (Base Case) ---
// 如果目前手裡的牌數已經達到目標數量 k
if (currentCombo.length === k) {
// 注意:必須使用 [...currentCombo] 建立一個「複本」存入筆記本
// 因為 currentCombo 在記憶體中是同一個位址,之後的 pop 操作會影響到它
results.push([...currentCombo]);
// 這條路走到底了,結束這一層遞迴,往回退 (Return)
return;
}
// --- 決策搜尋區域 ---
// 從 start 開始遍歷到陣列結尾
// start 的存在是為了「不回頭」,保證 [A, B] 跟 [B, A] 這種重複組合不會出現
for (let i = start; i < arr.length; i++) {
// 1. 【做決策】:把目前的元素放進手裡
currentCombo.push(arr[i]);
// 2. 【進入下一層】:帶著現在的手牌,從下一個位置 (i + 1) 繼續找下一張牌
// 這裡會一直往深處走,直到觸發上面的「終止條件」
backtrack(i + 1, currentCombo);
// 3. 【回溯 (後悔)】:這是最重要的一步!
// 當上一行 backtrack 結束回傳後,代表「以目前手牌開頭的所有可能性」都試完了
// 所以我們要將最後加入的元素「彈出 (pop)」,把空位留給迴圈的下一個元素
currentCombo.pop();
}
}
// 從索引 0 開始,帶著空空的手,開始第一次呼叫
backtrack(0, []);
// 最終返回裝滿組合的筆記本
return results;
}
const items = ['A', 'B', 'C', 'D', 'E'];
const allCombos = findCombinations(items, 2);
console.log(`總共有 ${allCombos.length} 組:`);
console.log(allCombos);
/* 輸出: [
['A','B','C'], ['A','B','D'], ['A','B','E'],
['A','C','D'], ['A','C','E'], ['A','D','E'],
['B','C','D'], ['B','C','E'], ['B','D','E'],
['C','D','E']
]
*/
```
---
---
---
---
---
---
---
---
---
---
---
---
---
---
### [談談 JavaScript 的 setTimeout 與 setInterval](https://kuro.tw/posts/2019/02/23/%E8%AB%87%E8%AB%87-JavaScript-%E7%9A%84-setTimeout-%E8%88%87-setInterval/)
```js
$('.result-html').html(md.render($('#markdowncontent').text()));
function refreshResult() {
if ("Changed" === $("#response").text()) {
$('.result-html').html(md.render($('#markdowncontent').val()));
}
window.setTimeout(refreshResult, 10000);
}
window.setTimeout(refreshResult, 10000);
```
### JSDoc
```js
/**
* 加法函式
* @param {number} a - 第一個加數
* @param {number} b - 第二個加數
* @returns {number} 兩個數字的和
* @example
* // 使用方式
* var result = add(5, 3);
* console.log(result); // 輸出 8
*/
function add(a, b) {
return a + b;
}
/**
* 計算數字的平方
* @param {number} num - 要計算平方的數字
* @returns {number} 平方值
* @description 這個函式接受一個數字作為參數,並返回該數字的平方值。
*/
function square(num) {
return num * num;
}
```
### setTimeout
該函式用於在指定的時間延遲後執行一次程式碼。
```js
setTimeout(function() {
console.log("這段程式碼將在 2 秒後執行");
}, 2000);
```
### setInterval
該函式用於以指定的間隔重複執行程式碼。
```js
// 使用 setInterval() 每隔 1 秒重複執行程式碼
let counter = 0;
const intervalId = setInterval(function() {
console.log("每秒執行一次,目前計數:" + counter);
counter++;
// 設定停止條件
if (counter === 5) {
clearInterval(intervalId); // 停止執行
}
}, 1000);
```
### Base64編碼和解碼
```js
// 编碼
var originalString = "Hello, World!";
var encodedString = btoa(originalString);
console.log("Encoded String: " + encodedString);
// 解碼
var decodedString = atob(encodedString);
console.log("Decoded String: " + decodedString);
```
### 將 Array 中第一個項目移至另一個 Array
```js
const array1 = [1, 2, 3, 4];
const array2 = [];
const firstItem = array1.shift(); // 移除 array1 的第一個項目並返回該項目
array2.push(firstItem); // 將該項目添加到 array2
console.log(array1); // 輸出:[2, 3, 4]
console.log(array2); // 輸出:[1]
```
### List
```js
var fruits = ["apple", "banana", "orange"];
var joinedFruits = fruits.join(", "); // 將陣列元素以逗號和空格分隔合併為一個字串
console.log(joinedFruits); // "apple, banana, orange"
var numbers = [1, 2, 3, 4, 5];
var joinedNumbers = numbers.join("-"); // 將陣列元素以破折號分隔合併為一個字串
console.log(joinedNumbers); // "1-2-3-4-5"
var text = "Hello";
var joinedText = Array.from(text).join(" "); // 將字串分隔合併為一個字串,以空格分隔字母
console.log(joinedText); // "H e l l o"
```
### Map
```js
// 定義一個文字對應函式的映射物件
const functionMap = {
'greet': function() {
console.log('Hello!');
},
'add': function(a, b) {
return a + b;
},
'multiply': function(a, b) {
return a * b;
}
};
// 使用映射物件執行相應的函式
functionMap['greet'](); // 印出 "Hello!"
const result1 = functionMap['add'](3, 5); // result1 = 8
const result2 = functionMap['multiply'](2, 4); // result2 = 8
```
### Class
```js
// 定義一個名為 "Person" 的類別
function Person(name, age) {
// 類別的建構子,用來初始化物件的屬性
this.name = name;
this.age = age;
}
// 在類別的原型上定義方法
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
// 在類別的原型上定義共用的屬性(可選)
Person.prototype.nationality = 'Unknown';
// 創建一個 Person 物件
const person1 = new Person('Alice', 25);
const person2 = new Person('Bob', 30);
// 呼叫物件的方法
person1.sayHello(); // 輸出:Hello, my name is Alice and I am 25 years old.
person2.sayHello(); // 輸出:Hello, my name is Bob and I am 30 years old.
// 存取共用的屬性
console.log(person1.nationality); // 輸出:Unknown
console.log(person2.nationality); // 輸出:Unknown
```
```js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
static staticMethod() {
console.log('This is a static method.');
}
}
const person1 = new Person('Alice', 25);
const person2 = new Person('Bob', 30);
person1.sayHello(); // 輸出:Hello, my name is Alice and I am 25 years old.
person2.sayHello(); // 輸出:Hello, my name is Bob and I am 30 years old.
Person.staticMethod(); // 輸出:This is a static method.
```
### MD5
```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
```
```js
// 要計算 MD5 的字串
const inputString = 'Hello, World!';
// 計算 MD5 雜湊
const md5Hash = CryptoJS.MD5(inputString);
// 將 MD5 雜湊轉換為字串
const md5String = md5Hash.toString();
// 輸出 MD5 雜湊
console.log(md5String);
```
### ISO 8601 Duration 時間操作
```html
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>日期操作範例</title>
<!-- 引入 luxon 库 -->
<script src="https://moment.github.io/luxon/global/luxon.js"></script>
</head>
<body>
<script>
function manipulateDate(inputDate, duration) {
// 將輸入的日期字串轉換為 DateTime 物件
const currentDate = luxon.DateTime.fromISO(inputDate);
// 解析 ISO 8601 持續時間
const durationObject = luxon.Duration.fromISO(duration);
// 根據持續時間調整日期
const manipulatedDate = currentDate.plus(durationObject);
// 格式化結果日期物件為 "YYYY-MM-DD" 格式的字串
const resultDateStr = manipulatedDate.toISODate();
return resultDateStr;
}
// 示例用法
const inputDate = "2023-11-16";
const duration1 = "P2D";
const duration2 = "-PT1H"; // 循環時間表示法,表示減少一小時
const resultDate1 = manipulateDate(inputDate, duration1);
const resultDate2 = manipulateDate(inputDate, duration2);
console.log(resultDate1); // 輸出: 2023-11-18
console.log(resultDate2); // 輸出: 2023-11-16
</script>
</body>
</html>
```
### == VS ===
在 JavaScript 中,`==` 和 `===` 是用來比較兩個值是否相等的運算符,但它們之間有一些重要的差異:
1. `==` (Equality Operator):
- 使用`==`進行比較時,JavaScript 會在比較之前進行類型轉換。
- 如果比較的兩個值的類型不同,JavaScript 會嘗試將它們轉換為相同的類型,然後再進行比較。
- 例如,`'5' == 5` 會返回 `true`,因為 JavaScript 會將字符串 `'5'` 轉換為數字 `5`,然後比較它們的值。
2. `===` (Strict Equality Operator):
- 使用`===`進行比較時,不會進行類型轉換。
- 如果比較的兩個值的類型不同,`===` 將直接返回 `false`,即使它們的值相等。
- 例如,`'5' === 5` 會返回 `false`,因為類型不同。
簡而言之,`==` 允許類型轉換,而 `===` 不允許類型轉換,並要求值和類型都相等。
建議使用 `===` 進行比較,因為它更嚴格,不會引起類型不明確的問題,有助於減少錯誤。
### onclick 帶參數
```html
<!DOCTYPE html>
<html>
<head>
<title>Button Onclick Function with Parameter Example</title>
<script>
function handleButtonOnClick(param) {
alert("Button clicked with parameter: " + param);
}
</script>
</head>
<body>
<button onclick="handleButtonOnClick('example')">Click Me</button>
</body>
</html>
```
在這個例子中,當按下按鈕時會觸發 `handleButtonOnClick` 函式,並且將字串 `example` 傳遞作為參數。當按下按鈕時,會彈出一個警告對話框,顯示出按鈕被點擊的訊息。
---
### 動態產生列表選單
```js
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>createFullSelectDOM 網頁範例</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
margin: 40px auto;
max-width: 600px;
background-color: #f8f9fa;
color: #333;
}
.card {
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
h2 {
margin-top: 0;
color: #2c3e50;
border-bottom: 2px solid #eee;
padding-bottom: 10px;
}
.btn-group {
margin-bottom: 20px;
}
button {
background-color: #4f46e5;
color: white;
border: none;
padding: 10px 16px;
font-size: 14px;
border-radius: 6px;
cursor: pointer;
margin-right: 10px;
transition: background 0.2s;
}
button:hover {
background-color: #4338ca;
}
button.secondary {
background-color: #10b981;
}
button.secondary:hover {
background-color: #059669;
}
/* 幫動態生成的 select 加上漂亮樣式 */
select {
display: block;
width: 100%;
padding: 10px 12px;
font-size: 16px;
border: 1px solid #d1d5db;
border-radius: 6px;
background-color: #fff;
margin-top: 10px;
outline: none;
box-shadow: inset 0 1px 2px rgba(0,0,0,0.05);
}
select:focus {
border-color: #4f46e5;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
}
.log-box {
margin-top: 20px;
padding: 12px;
background-color: #f1f5f9;
border-radius: 6px;
font-family: monospace;
font-size: 14px;
color: #475569;
}
</style>
</head>
<body>
<div class="card">
<h2>🎯 createFullSelectDOM 實戰範例</h2>
<div class="btn-group">
<button id="btnDept">🔄 載入/替換為【部門選單】</button>
<button id="btnProd" class="secondary">➕ 串接追加【產品選單】</button>
</div>
<div id="menuContainer">
<p style="color: #9ca3af;">目前尚未生成任何選單,請點擊上方按鈕...</p>
</div>
<div class="log-box" id="logBox">目前無選取事件</div>
</div>
<script>
/**
* 建立完整的 <select> 下拉式選單 DOM 物件
* @param {Array<Object>} list - 包含數個物件的串列
* @param {string} textKey - 物件中用來當作選單「顯示文案」的欄位名稱
* @param {string} [valueKey='id'] - 物件中用來當作選單「值 (Value)」的欄位名稱
* @param {Object} [options={}] - 額外的自訂配置
* @param {string} [options.id=''] - select 的 HTML ID
* @param {string} [options.className=''] - select 的 CSS Class 名稱
* @param {string} [options.placeholder=''] - 預設的第一個提示選項
* @returns {HTMLSelectElement} 組合好的完整 <select> DOM 物件
*/
function createFullSelectDOM(list, textKey, valueKey = 'id', options = {}) {
// 1. 🚀 建立 select 元素
const selectElem = document.createElement('select');
// 2. 🚀 設定 ID 與 Class
if (options.id) selectElem.id = options.id;
if (options.className) selectElem.className = options.className;
// 3. 🚀 處理預設的第一個提示選項 (例如:-- 請選擇 --)
if (options.placeholder) {
const placeholderOption = document.createElement('option');
placeholderOption.value = "";
placeholderOption.textContent = options.placeholder; // 使用 textContent 確保安全
selectElem.appendChild(placeholderOption);
}
// 4. 🚀 檢查傳入是否為陣列,若不是則直接回傳空的 select 物件
if (!Array.isArray(list)) return selectElem;
// 5. 🚀 迴圈建立每個 <option> 並塞入 <select>
list.forEach(item => {
const optionElem = document.createElement('option');
optionElem.value = item[valueKey];
optionElem.textContent = item[textKey]; // 安全地注入文案
selectElem.appendChild(optionElem);
});
// 6. 🚀 回傳真實的 DOM 物件
return selectElem;
}
// ==========================================
// 📋 模擬測試資料來源
// ==========================================
const departments = [
{ depCode: "D01", depName: "資訊研發部" },
{ depCode: "D02", depName: "數位金融處" },
{ depCode: "D03", depName: "大數據分析組" }
];
const products = [
{ prodId: "P_MOUSE", prodName: "人體工學滑鼠" },
{ prodId: "P_KEYBOARD", prodName: "無線藍牙鍵盤" }
];
// ==========================================
// ⚙️ 網頁 DOM 事件綁定
// ==========================================
const container = document.getElementById('menuContainer');
const logBox = document.getElementById('logBox');
// 監聽器函式:當選單改變時,把值吐在 logBox
function handleSelectChange(e) {
logBox.textContent = `🔔 觸發 Change 事件!選單 ID: "${e.target.id}",選中的 Value: "${e.target.value}"`;
}
// 1. 【替換】按鈕事件
document.getElementById('btnDept').addEventListener('click', () => {
// 產生選單 DOM
const deptSelect = createFullSelectDOM(departments, "depName", "depCode", {
id: "deptSelector",
placeholder: "--- 請選擇所屬部門 ---"
});
// 💡 因為回傳的是真正的 DOM 元素,可以直接綁定事件監聽!
deptSelect.addEventListener('change', handleSelectChange);
// 執行「替換」:先清空容器,再塞入新節點
container.innerHTML = "";
container.appendChild(deptSelect);
logBox.textContent = "✅ 已成功「替換」為部門選單。";
});
// 2. 【串接追加】按鈕事件
document.getElementById('btnProd').addEventListener('click', () => {
// 產生另一個選單 DOM
const prodSelect = createFullSelectDOM(products, "prodName", "prodId", {
id: "prodSelector_" + Date.now(), // 給予動態 ID
placeholder: "--- 挑選一個尾牙禮品 ---"
});
prodSelect.addEventListener('change', handleSelectChange);
// 💡 如果容器內原本只有預設的提示文字,先清空它
if (container.querySelector('p')) {
container.innerHTML = "";
}
// 執行「串接」:不影響舊有的,直接 appendChild 追加到最後面
container.appendChild(prodSelect);
logBox.textContent = "✅ 已成功「串接追加」一個產品選單。";
});
</script>
</body>
</html>
```