JavaScript 基礎:DOM 操作
各位好!
這篇是 JavaScript 系列的第十一單元,我們要學習 DOM(Document Object Model)操作。DOM 讓我們可以用 JavaScript 控制網頁的內容和樣式。
什麼是 DOM?
DOM 是瀏覽器提供的介面,將 HTML 轉換成樹狀結構,讓 JavaScript 可以存取和修改。
<!DOCTYPE html>
<html>
<head>
<title>範例</title>
</head>
<body>
<div id="app">
<h1>標題</h1>
<p>段落</p>
</div>
</body>
</html>
<!-- DOM 樹結構:
Document
└─ html
├─ head
│ └─ title
│ └─ "範例"
└─ body
└─ div#app
├─ h1
│ └─ "標題"
└─ p
└─ "段落"
-->
選取元素
document.getElementById()
// HTML: <div id="app">內容</div>
const app = document.getElementById("app");
console.log(app); // <div id="app">內容</div>
// 如果找不到回傳 null
const notExist = document.getElementById("notExist");
console.log(notExist); // null
document.querySelector()
使用 CSS 選擇器選取第一個符合的元素。
// HTML:
// <div class="container">
// <p class="text">第一段</p>
// <p class="text">第二段</p>
// </div>
// 選取第一個 .text
const firstText = document.querySelector(".text");
console.log(firstText.textContent); // "第一段"
// 選取 id
const app = document.querySelector("#app");
// 複雜選擇器
const p = document.querySelector(".container > p.text");
// 屬性選擇器
const input = document.querySelector("input[type='email']");
// 偽類選擇器
const firstChild = document.querySelector("ul li:first-child");
document.querySelectorAll()
選取所有符合的元素,回傳 NodeList。
// 選取所有 .text
const allTexts = document.querySelectorAll(".text");
console.log(allTexts.length); // 2
// NodeList 可以用 forEach
allTexts.forEach(text => {
console.log(text.textContent);
});
// 或轉成陣列
const textsArray = Array.from(allTexts);
const textsArray2 = [...allTexts];
// 用陣列方法
const contents = [...allTexts].map(text => text.textContent);
其他選取方法
// getElementsByClassName - 回傳 HTMLCollection
const items = document.getElementsByClassName("item");
// getElementsByTagName - 回傳 HTMLCollection
const paragraphs = document.getElementsByTagName("p");
// getElementsByName - 通常用於表單
const radios = document.getElementsByName("gender");
// HTMLCollection 是「活的」,會自動更新
// NodeList(querySelectorAll)是「靜態的」
// 建議:優先使用 querySelector 和 querySelectorAll
修改內容
textContent
const heading = document.querySelector("h1");
// 讀取文字內容
console.log(heading.textContent);
// 設定文字內容
heading.textContent = "新標題";
// textContent 會將 HTML 標籤當作純文字
heading.textContent = "<strong>粗體</strong>";
// 顯示:<strong>粗體</strong>(不是粗體效果)
innerHTML
const container = document.querySelector(".container");
// 讀取 HTML 內容
console.log(container.innerHTML);
// 設定 HTML 內容
container.innerHTML = "<p>新段落</p>";
// 可以插入 HTML 標籤
container.innerHTML = "<strong>粗體</strong>";
// 顯示:粗體(有粗體效果)
// 小心:innerHTML 有 XSS 風險
const userInput = "<img src=x onerror='alert(1)'>";
container.innerHTML = userInput; // 危險!會執行 JavaScript
// 安全做法:使用 textContent 或先清理輸入
innerText vs textContent
// HTML:
// <div id="test">
// <p>可見文字</p>
// <p style="display: none;">隱藏文字</p>
// </div>
const div = document.querySelector("#test");
// textContent:取得所有文字(包含隱藏的)
console.log(div.textContent); // "可見文字隱藏文字"
// innerText:只取得可見的文字
console.log(div.innerText); // "可見文字"
// 建議:使用 textContent(效能較好)
修改屬性
getAttribute / setAttribute
const link = document.querySelector("a");
// 取得屬性
const href = link.getAttribute("href");
console.log(href);
// 設定屬性
link.setAttribute("href", "https://example.com");
link.setAttribute("target", "_blank");
// 移除屬性
link.removeAttribute("target");
// 檢查屬性是否存在
if (link.hasAttribute("href")) {
console.log("有 href 屬性");
}
直接存取屬性
const input = document.querySelector("input");
// 讀取
console.log(input.value);
console.log(input.type);
console.log(input.placeholder);
// 設定
input.value = "新值";
input.type = "password";
input.placeholder = "請輸入密碼";
// 布林屬性
input.disabled = true;
input.checked = true;
input.required = true;
// 常見屬性
const img = document.querySelector("img");
img.src = "image.jpg";
img.alt = "圖片說明";
const link = document.querySelector("a");
link.href = "https://example.com";
link.target = "_blank";
data 屬性
// HTML: <div id="user" data-id="123" data-role="admin">
const user = document.querySelector("#user");
// 使用 dataset
console.log(user.dataset.id); // "123"
console.log(user.dataset.role); // "admin"
// 設定
user.dataset.status = "active";
// 產生:<div data-status="active">
// 多字屬性(使用小駝峰)
user.dataset.firstName = "Alice";
// 產生:<div data-first-name="Alice">
// 刪除
delete user.dataset.status;
修改樣式
style 屬性
const box = document.querySelector(".box");
// 讀取行內樣式
console.log(box.style.color);
// 設定單一樣式
box.style.color = "red";
box.style.backgroundColor = "blue"; // CSS 的 background-color
box.style.fontSize = "20px";
// 一次設定多個樣式(使用 cssText)
box.style.cssText = "color: red; background-color: blue; font-size: 20px;";
// 移除樣式
box.style.color = "";
// 注意:style 只能存取行內樣式,無法取得 CSS 檔案的樣式
getComputedStyle()
// 取得計算後的樣式(包含 CSS 檔案的樣式)
const box = document.querySelector(".box");
const styles = getComputedStyle(box);
console.log(styles.color);
console.log(styles.backgroundColor);
console.log(styles.width); // 實際寬度,如 "200px"
classList
const box = document.querySelector(".box");
// 新增 class
box.classList.add("active");
box.classList.add("highlight", "large"); // 一次加多個
// 移除 class
box.classList.remove("active");
box.classList.remove("highlight", "large");
// 切換 class(有就移除,沒有就加上)
box.classList.toggle("active");
// 檢查是否有某個 class
if (box.classList.contains("active")) {
console.log("有 active class");
}
// 取代 class
box.classList.replace("old-class", "new-class");
// 取得所有 class
console.log(box.classList); // DOMTokenList
// 轉成陣列
const classes = [...box.classList];
建立和刪除元素
建立元素
// 建立元素
const div = document.createElement("div");
const p = document.createElement("p");
const span = document.createElement("span");
// 設定內容
div.textContent = "這是一個 div";
p.innerHTML = "<strong>粗體文字</strong>";
// 設定屬性
div.id = "myDiv";
div.className = "container";
p.classList.add("text");
// 建立文字節點
const textNode = document.createTextNode("純文字");
插入元素
const container = document.querySelector(".container");
const newElement = document.createElement("p");
newElement.textContent = "新段落";
// appendChild - 加到最後
container.appendChild(newElement);
// insertBefore - 插入到指定元素前
const firstChild = container.firstElementChild;
container.insertBefore(newElement, firstChild);
// append - 可以加多個元素或文字
container.append(newElement, "純文字", anotherElement);
// prepend - 加到最前面
container.prepend(newElement);
// before / after - 插入到元素前後
const target = document.querySelector("#target");
target.before(newElement); // 插入到 target 前面
target.after(newElement); // 插入到 target 後面
// insertAdjacentHTML - 插入 HTML 字串
container.insertAdjacentHTML("beforebegin", "<p>前面</p>");
container.insertAdjacentHTML("afterbegin", "<p>開頭</p>");
container.insertAdjacentHTML("beforeend", "<p>結尾</p>");
container.insertAdjacentHTML("afterend", "<p>後面</p>");
刪除元素
const element = document.querySelector(".to-remove");
// remove - 移除元素本身
element.remove();
// removeChild - 移除子元素
const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
parent.removeChild(child);
// 清空所有子元素
parent.innerHTML = "";
// 或
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
複製元素
const original = document.querySelector(".original");
// 淺複製(不包含子元素)
const shallowCopy = original.cloneNode(false);
// 深複製(包含所有子元素)
const deepCopy = original.cloneNode(true);
// 插入複製的元素
document.body.appendChild(deepCopy);
事件處理
addEventListener
const button = document.querySelector("button");
// 基本用法
button.addEventListener("click", function() {
console.log("按鈕被點擊");
});
// 使用箭頭函式
button.addEventListener("click", () => {
console.log("按鈕被點擊");
});
// 事件物件
button.addEventListener("click", (event) => {
console.log(event.type); // "click"
console.log(event.target); // 被點擊的元素
console.log(event.currentTarget); // 綁定事件的元素
});
// 移除事件監聽器
function handleClick() {
console.log("點擊");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick);
// 注意:箭頭函式無法移除
button.addEventListener("click", () => {});
button.removeEventListener("click", () => {}); // 無效!
常見事件
// 滑鼠事件
element.addEventListener("click", () => {}); // 點擊
element.addEventListener("dblclick", () => {}); // 雙擊
element.addEventListener("mousedown", () => {}); // 按下
element.addEventListener("mouseup", () => {}); // 放開
element.addEventListener("mousemove", () => {}); // 移動
element.addEventListener("mouseenter", () => {}); // 進入
element.addEventListener("mouseleave", () => {}); // 離開
// 鍵盤事件
input.addEventListener("keydown", (e) => {
console.log(e.key); // 按下的鍵
console.log(e.code); // 鍵盤代碼
console.log(e.ctrlKey); // 是否按 Ctrl
console.log(e.shiftKey); // 是否按 Shift
});
input.addEventListener("keyup", () => {});
input.addEventListener("keypress", () => {}); // 已廢棄
// 表單事件
form.addEventListener("submit", (e) => {
e.preventDefault(); // 阻止表單提交
});
input.addEventListener("input", () => {}); // 輸入中
input.addEventListener("change", () => {}); // 值改變
input.addEventListener("focus", () => {}); // 取得焦點
input.addEventListener("blur", () => {}); // 失去焦點
// 其他事件
window.addEventListener("load", () => {}); // 頁面載入完成
window.addEventListener("resize", () => {}); // 視窗大小改變
window.addEventListener("scroll", () => {}); // 捲動
document.addEventListener("DOMContentLoaded", () => {}); // DOM 載入完成
事件委派(Event Delegation)
// 不好:為每個子元素綁定事件
const items = document.querySelectorAll(".item");
items.forEach(item => {
item.addEventListener("click", () => {
console.log("點擊項目");
});
});
// 好:使用事件委派
const list = document.querySelector(".list");
list.addEventListener("click", (event) => {
// 檢查被點擊的元素
if (event.target.classList.contains("item")) {
console.log("點擊項目");
}
});
// 實用範例:動態新增的元素也能觸發事件
const container = document.querySelector(".container");
container.addEventListener("click", (event) => {
if (event.target.classList.contains("delete-btn")) {
event.target.closest(".item").remove();
}
});
// 之後新增的 .delete-btn 也會有效
const newItem = document.createElement("div");
newItem.innerHTML = '<button class="delete-btn">刪除</button>';
container.appendChild(newItem);
阻止預設行為和冒泡
// 阻止預設行為
const link = document.querySelector("a");
link.addEventListener("click", (event) => {
event.preventDefault(); // 不跳轉連結
console.log("連結被點擊,但不跳轉");
});
form.addEventListener("submit", (event) => {
event.preventDefault(); // 不提交表單
// 自訂處理邏輯
});
// 阻止事件冒泡
const parent = document.querySelector(".parent");
const child = document.querySelector(".child");
parent.addEventListener("click", () => {
console.log("父元素被點擊");
});
child.addEventListener("click", (event) => {
event.stopPropagation(); // 阻止冒泡到父元素
console.log("子元素被點擊");
});
// 點擊 child 只會輸出「子元素被點擊」
表單處理
取得表單值
// HTML:
// <form id="myForm">
// <input type="text" name="username">
// <input type="email" name="email">
// <input type="checkbox" name="agree">
// <button type="submit">送出</button>
// </form>
const form = document.querySelector("#myForm");
form.addEventListener("submit", (event) => {
event.preventDefault();
// 方法一:直接存取
const username = form.querySelector('[name="username"]').value;
const email = form.querySelector('[name="email"]').value;
const agree = form.querySelector('[name="agree"]').checked;
console.log({ username, email, agree });
// 方法二:使用 FormData
const formData = new FormData(form);
const data = Object.fromEntries(formData);
console.log(data);
// FormData 的方法
console.log(formData.get("username"));
console.log(formData.has("email"));
formData.set("username", "新值");
formData.delete("agree");
// 遍歷
for (const [key, value] of formData) {
console.log(`${key}: ${value}`);
}
});
表單驗證
const form = document.querySelector("#myForm");
const usernameInput = form.querySelector('[name="username"]');
const emailInput = form.querySelector('[name="email"]');
form.addEventListener("submit", (event) => {
event.preventDefault();
let isValid = true;
// 驗證使用者名稱
if (usernameInput.value.trim() === "") {
showError(usernameInput, "使用者名稱不能為空");
isValid = false;
} else {
clearError(usernameInput);
}
// 驗證 email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(emailInput.value)) {
showError(emailInput, "請輸入有效的 email");
isValid = false;
} else {
clearError(emailInput);
}
if (isValid) {
console.log("表單驗證通過");
// 送出資料
}
});
function showError(input, message) {
const parent = input.parentElement;
let error = parent.querySelector(".error");
if (!error) {
error = document.createElement("span");
error.className = "error";
parent.appendChild(error);
}
error.textContent = message;
input.classList.add("invalid");
}
function clearError(input) {
const parent = input.parentElement;
const error = parent.querySelector(".error");
if (error) {
error.remove();
}
input.classList.remove("invalid");
}
實戰專案
待辦清單
const form = document.querySelector("#todoForm");
const input = document.querySelector("#todoInput");
const list = document.querySelector("#todoList");
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = input.value.trim();
if (text === "") return;
addTodo(text);
input.value = "";
});
function addTodo(text) {
const li = document.createElement("li");
li.innerHTML = `
<span class="todo-text">${text}</span>
<button class="delete-btn">刪除</button>
`;
// 完成切換
li.querySelector(".todo-text").addEventListener("click", () => {
li.classList.toggle("completed");
});
// 刪除
li.querySelector(".delete-btn").addEventListener("click", () => {
li.remove();
});
list.appendChild(li);
}
// HTML:
// <form id="todoForm">
// <input id="todoInput" type="text" placeholder="新增待辦事項">
// <button type="submit">新增</button>
// </form>
// <ul id="todoList"></ul>
// CSS:
// .completed .todo-text { text-decoration: line-through; }
圖片輪播
const slides = document.querySelectorAll(".slide");
const prevBtn = document.querySelector(".prev");
const nextBtn = document.querySelector(".next");
let currentIndex = 0;
function showSlide(index) {
slides.forEach((slide, i) => {
slide.classList.toggle("active", i === index);
});
}
function nextSlide() {
currentIndex = (currentIndex + 1) % slides.length;
showSlide(currentIndex);
}
function prevSlide() {
currentIndex = (currentIndex - 1 + slides.length) % slides.length;
showSlide(currentIndex);
}
nextBtn.addEventListener("click", nextSlide);
prevBtn.addEventListener("click", prevSlide);
// 自動輪播
let autoPlay = setInterval(nextSlide, 3000);
// 滑鼠移入暫停
document.querySelector(".slider").addEventListener("mouseenter", () => {
clearInterval(autoPlay);
});
document.querySelector(".slider").addEventListener("mouseleave", () => {
autoPlay = setInterval(nextSlide, 3000);
});
// 初始顯示
showSlide(0);
模態框(Modal)
const openBtn = document.querySelector("#openModal");
const closeBtn = document.querySelector("#closeModal");
const modal = document.querySelector(".modal");
const overlay = document.querySelector(".overlay");
function openModal() {
modal.classList.add("active");
overlay.classList.add("active");
document.body.style.overflow = "hidden";
}
function closeModal() {
modal.classList.remove("active");
overlay.classList.remove("active");
document.body.style.overflow = "";
}
openBtn.addEventListener("click", openModal);
closeBtn.addEventListener("click", closeModal);
overlay.addEventListener("click", closeModal);
// ESC 鍵關閉
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && modal.classList.contains("active")) {
closeModal();
}
});
小結
這篇我們學習了:
- 選取 DOM 元素的各種方法
- 修改元素的內容、屬性、樣式
- 建立、插入、刪除元素
- 事件處理與事件委派
- 表單操作與驗證
- 實用的 DOM 專案
下一步:
完成這篇後,你已經能夠: - 操作網頁上的任何元素 - 處理使用者互動 - 建立動態網頁應用
前往下一篇:單元十二:ES6+ 現代語法總整理
在那裡,我們會回顧所有現代 JavaScript 語法。
0 留言
發表留言