JavaScript 相關

Posted by Adam on August 24, 2022
# 📘 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> ```