什麼是 DOM?
當瀏覽器載入 HTML 檔案時,會將其解析成一個樹狀結構,稱為 DOM 樹。每個 HTML 標籤(如 <div>、<p>、<button>)都會轉換為一個節點(Node),這些節點之間存在父子兄弟的層級關係。透過這個樹狀結構,JavaScript 可以:
- 選取特定的 HTML 元素
- 讀取或修改元素的內容與屬性
- 動態新增或刪除元素
- 監聽使用者的操作並做出回應
- 改變元素的樣式與CSS類別
理解 DOM 是成為優秀前端開發者的必備技能。無論是建立簡單的表單驗證,還是開發複雜的單頁應用程式(SPA),DOM 操作都是不可或缺的基礎。
元素選取器
在操作 DOM 之前,我們需要先選取要操作的元素。JavaScript 提供了多種選取元素的方法,以下是最常用的幾種:
getElementById
getElementById 是最直接的元素選取方式,透過元素的 id 屬性來取得單一元素。由於 HTML 中 id 應該是唯一的,因此這個方法會回傳一個元素物件,或在找不到時回傳 null。
// 選取 id 為 "title" 的元素
const title = document.getElementById('title');
// 檢查元素是否存在
if (title) {
title.textContent = '新標題';
}
querySelector 與 querySelectorAll
querySelector 讓您使用 CSS 選擇器語法來選取元素,這提供了極大的彈性。querySelector 回傳第一個符合的元素,而 querySelectorAll 回傳一個 NodeList(節點列表),包含所有符合條件的元素。
// 選取第一個 class 為 "card" 的元素
const card = document.querySelector('.card');
// 選取所有 li 元素
const listItems = document.querySelectorAll('li');
// 選取特定的子元素
const submitBtn = document.querySelector('form button[type="submit"]');
// 遍歷所有選取的元素
listItems.forEach((item, index) => {
item.textContent = `項目 ${index + 1}`;
});
其他常用的選取方法
| 方法 | 說明 | 回傳類型 |
|---|---|---|
getElementsByClassName |
透過類別名稱選取 | HTMLCollection |
getElementsByTagName |
透過標籤名稱選取 | HTMLCollection |
getElementsByName |
透過 name 屬性選取 | NodeList |
getElementById,因為它是原生方法中效能最好的。若需要複雜的選擇邏輯,querySelector 和 querySelectorAll 是最靈活的選擇。
事件處理
網頁的互動性來自於事件(Events)的處理。當使用者點擊按鈕、輸入文字、或移動滑鼠時,瀏覽器會產生對應的事件。透過 addEventListener 方法,我們可以註冊事件監聽器來回應這些操作。
addEventListener 基本用法
const button = document.querySelector('#myButton');
// 註冊點擊事件監聽器
button.addEventListener('click', function(event) {
console.log('按鈕被點擊了!');
});
// 使用箭頭函式
button.addEventListener('click', (e) => {
e.target.style.backgroundColor = 'var(--accent)';
});
常見的事件類型
| 事件類型 | 說明 | 適用元素 |
|---|---|---|
click |
元素被點擊 | 所有可點擊元素 |
mouseenter/mouseleave |
滑鼠進入/離開元素 | 幾乎所有元素 |
keydown/keyup |
鍵盤按鍵按下/釋放 | 文件、輸入框 |
submit |
表單提交 | form 元素 |
input |
輸入值變化 | input, textarea |
change |
元素值變更確認 | select, input |
load |
資源載入完成 | window, img, script |
事件委託(Event Delegation)
事件委託是一種高效的事件處理模式。與其為每個子元素個別監聽事件,不如將監聽器附加到父元素上,利用事件冒泡(Event Bubbling)的特性來處理。這在動態生成的元素上特別有用。
// 為父容器註冊單一監聽器,處理所有子按鈕的點擊
const container = document.querySelector('#buttonContainer');
container.addEventListener('click', (e) => {
// 檢查點擊的目標是否是按鈕
if (e.target.classList.contains('btn')) {
console.log(' clicked:', e.target.textContent);
}
});
事件物件(Event Object)
當事件觸發時,事件處理函式會接收一個事件物件作為參數。這個物件包含了豐富的資訊,讓我們能夠了解事件的詳細情況並做出適當的回應。
常見的事件物件屬性
target:觸發事件的實際元素currentTarget:註冊監聽器的元素(即this)type:事件的類型名稱preventDefault():阻止預設行為stopPropagation():阻止事件冒泡clientX/clientY:滑鼠游標的視口座標
document.addEventListener('click', (e) => {
console.log('事件類型:', e.type);
console.log('目標元素:', e.target.tagName);
console.log('游標位置:', e.clientX, e.clientY);
});
// 阻止連結的預設跳轉行為
const link = document.querySelector('#externalLink');
link.addEventListener('click', (e) => {
e.preventDefault();
console.log('連結跳轉已阻止');
});
動態建立與修改元素
JavaScript 可以讓我們在網頁載入後動態地新增、修改或刪除元素。這是建立互動式應用的基礎,讓頁面能夠根據使用者的操作即時更新內容。
建立新元素
使用 createElement 方法可以建立新的 HTML 元素。這個方法接受標籤名稱作為參數,回傳一個新的元素節點。
// 建立新的 div 元素
const newDiv = document.createElement('div');
newDiv.className = 'card';
newDiv.textContent = '這是新建立的元素';
// 建立包含 HTML 內容的元素
const article = document.createElement('article');
article.innerHTML = `
<h3>文章標題</h3>
<p>文章內容...</p>
`;
新增元素到 DOM
建立元素後,需要將它加入到 DOM 樹中才會顯示。appendChild 會將元素加入父元素的最後一個子節點之後,而 insertBefore 則可以在特定位置插入元素。
// 假設有一個容器
const container = document.querySelector('#container');
// 建立新元素
const newItem = document.createElement('li');
newItem.textContent = '新項目';
// 加入到容器
container.appendChild(newItem);
// 在第一個子元素之前插入
const firstChild = container.firstElementChild;
if (firstChild) {
container.insertBefore(newItem, firstChild);
}
刪除元素
刪除元素有兩種主要方式:使用父元素的 removeChild 方法,或直接呼叫元素本身的 remove 方法。
// 方式一:使用 removeChild
const parent = document.querySelector('#list');
const toRemove = document.querySelector('.item');
parent.removeChild(toRemove);
// 方式二:直接呼叫 remove(現代瀏覽器)
toRemove.remove();
// 清除所有子元素
while (container.firstChild) {
container.removeChild(container.firstChild);
}
修改元素內容與屬性
// 修改文字內容
element.textContent = '新文字';
// 修改 HTML 內容(小心 XSS 攻擊)
element.innerHTML = '<strong>粗體文字</strong>';
// 修改屬性
image.src = 'new-image.jpg';
image.alt = '新圖片描述';
// 操作類別
element.classList.add('active');
element.classList.remove('hidden');
element.classList.toggle('expanded');
element.classList.contains('active'); // 回傳 true/false
// 修改行內樣式
element.style.color = '#ff0000';
element.style.display = 'none';
表單處理
表單是網頁與使用者互動的重要媒介。JavaScript 提供了豐富的 API 來處理表單輸入、表單驗證,以及提交事件。
取得表單資料
// 透過表單元素的名稱取得輸入值
const form = document.querySelector('#contactForm');
// 監聽表單提交事件
form.addEventListener('submit', (e) => {
e.preventDefault(); // 阻止表單實際提交
// 取得表單資料
const formData = new FormData(form);
const name = formData.get('name');
const email = formData.get('email');
console.log('姓名:', name);
console.log('電子郵件:', email);
});
即時輸入驗證
// 即時監聽輸入事件
const emailInput = document.querySelector('#email');
const errorMsg = document.querySelector('#emailError');
emailInput.addEventListener('input', () => {
const email = emailInput.value;
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
errorMsg.textContent = '請輸入有效的電子郵件地址';
emailInput.classList.add('error');
} else {
errorMsg.textContent = '';
emailInput.classList.remove('error');
}
});
常用表單事件
focus:輸入框獲得焦點blur:輸入框失去焦點input:輸入值變化時觸發(即時)change:輸入值變更且失去焦點後觸發submit:表單提交時觸發reset:表單重設時觸發
實用練習題
練習一:待辦事項清單
建立一個簡單的待辦事項應用程式,功能包括:
- 輸入新項目並按下 Enter 或點擊按鈕新增到清單
- 點擊項目可標記為已完成(刪除線效果)
- 點擊刪除按鈕可移除該項目
- 顯示目前項目總數
提示:使用 createElement 建立新項目,addEventListener 監聽點擊和輸入事件,appendChild 加入新元素,remove 刪除元素。
練習二:圖片點擊計數器
製作一個點擊計數器,功能包括:
- 點擊圖片時顯示「已點擊 N 次」
- 滑鼠移到圖片上時放大圖片
- 滑鼠離開時恢復原始大小
提示:利用事件物件的 target 屬性操作圖片,使用 style.transform 改變大小。
練習三:表單驗證系統
建立包含以下欄位的表單驗證:
- 使用者名稱(至少 3 個字元)
- 電子郵件(符合 Email 格式)
- 密碼(至少 8 個字元,包含數字和字母)
- 密碼確認(需與密碼相同)
提示:使用正規表達式驗證格式,在 submit 事件中檢查所有欄位,若驗證失敗呼叫 preventDefault 阻止提交。
最佳實踐建議
- 快取 DOM 查詢結果:如果需要多次訪問同一元素,先將其存入變數,避免重複查詢。
- 事件委託:對於大量相似元素的監聽,優先使用事件委託以提升效能。
- 清理事件監聽器:在元素被移除前,考慮移除相關的事件監聽器,防止記憶體洩漏。
- XSS 防護:使用
textContent而非innerHTML來設置使用者輸入的文字內容。 - 使用 CSS 類別管理樣式:盡量透過新增或移除類別來改變樣式,而非直接操作
style屬性。
MutationObserver、IntersectionObserver)以及前端框架(如 React、Vue)的元件化開發方式,它們在 DOM 操作之上提供了更高效的抽象層。