JavaScript – DOM 與瀏覽器 API
單元:DOM 結構與節點類型
簡介
在前端開發中,DOM(Document Object Model) 是連結 HTML 與 JavaScript 的橋樑。瀏覽器會把一張 HTML 文件解析成一棵樹狀結構,開發者便可以透過程式碼「讀取」或「修改」這棵樹,從而動態改變頁面的內容與行為。
如果不了解 DOM 的基本結構與各種節點(Node)類型,就很難寫出可維護、效能良好的程式。相反地,掌握這些概念後,我們可以更精準地操作元素、插入文字、移除註解,甚至在不觸發重排(reflow)的情況下批次更新頁面。
本篇文章將從 DOM 樹的層級、節點類型、以及 常見操作 三個面向,深入淺出地說明它們的特性與實務應用,幫助你在開發過程中少走彎路、寫出更乾淨的程式碼。
核心概念
1. DOM 樹的基本結構
DOM 把一個 HTML 文件視為一棵節點樹(Node Tree),根節點是 Document 物件,下面依序是:
| 層級 | 節點名稱 | 說明 |
|---|---|---|
| 0 | Document |
代表整個文件本身,唯一且不可被刪除。 |
| 1 | DocumentType (<!DOCTYPE html>) |
定義文件類型,位於根節點之下。 |
| 2 | Element (<html>) |
主要的 HTML 標籤,通常稱為 根元素。 |
| 3+ | Element、Text、Comment… |
依照 HTML 結構往下展開。 |
小技巧:在瀏覽器 console 中輸入
document.documentElement,即可直接取得<html>元素,這是檢視整棵樹的起點。
2. Node 介面的屬性與方法
所有節點(不論是 Element、Text、Comment)都繼承自 Node 介面,常用的屬性包括:
| 屬性 | 說明 |
|---|---|
nodeType |
以數值表示的節點類型(1=Element、3=Text、8=Comment、9=Document 等)。 |
nodeName |
節點的名稱,對於元素是標籤名稱(大寫),文字節點則是 #text。 |
childNodes |
包含所有子節點的 NodeList(包括文字與註解)。 |
parentNode |
父節點參考。 |
firstChild / lastChild |
第一或最後一個子節點。 |
提醒:
children只返回元素節點(Element),而childNodes會把文字、註解等全部列出。
3. 常見節點類型
| 類型 | nodeType 值 |
範例 | 典型用途 |
|---|---|---|---|
Document |
9 | document |
整體文件操作(如 createElement)。 |
DocumentType |
10 | <!DOCTYPE html> |
指定文件類型,通常不需要操作。 |
Element |
1 | <div>、<p> |
大部分 UI 操作的目標。 |
Text |
3 | 文字節點 "Hello" |
內文、文字節點的插入與修改。 |
Comment |
8 | <!-- comment --> |
開發除錯或說明用,通常不會顯示。 |
DocumentFragment |
11 | document.createDocumentFragment() |
暫存多筆節點,批次插入以降低重排。 |
程式碼範例
以下範例示範 5 種常見操作,每段程式碼均附有說明註解,方便直接貼上測試。
1️⃣ 取得不同類型的節點與屬性
// 取得根元素 <html>
const htmlEl = document.documentElement; // Element
console.log(htmlEl.nodeName); // "HTML"
console.log(htmlEl.nodeType); // 1 (Element)
// 取得 <body> 裡的第一個子節點(可能是文字或註解)
const firstChild = document.body.firstChild;
console.log(firstChild.nodeName); // "#text"、"#comment" 或 標籤名稱
console.log(firstChild.nodeType); // 3、8 或 1
// 只想拿到真正的元素子節點,可使用 children
console.log(document.body.children.length); // 只算 <div>、<p> … 等元素
2️⃣ 建立與插入文字節點
// 建立 <p> 元素
const p = document.createElement('p');
// 建立文字節點
const txt = document.createTextNode('Hello, DOM!');
// 把文字節點掛到 <p> 裡
p.appendChild(txt);
// 把 <p> 加到 <body> 的最後面
document.body.appendChild(p);
3️⃣ 使用 DocumentFragment 批次插入多筆節點
// 假設要一次加入 100 個 <li>
const ul = document.createElement('ul');
const fragment = document.createDocumentFragment(); // 暫存容器
for (let i = 1; i <= 100; i++) {
const li = document.createElement('li');
li.textContent = `項目 ${i}`;
fragment.appendChild(li); // 只在 fragment 裡操作,不會觸發重排
}
// 最後一次性插入到 <ul>,只產生一次重排
ul.appendChild(fragment);
document.body.appendChild(ul);
4️⃣ 替換、移除與克隆節點
// 取得目標元素
const oldDiv = document.querySelector('#old');
// 建立新元素
const newDiv = document.createElement('div');
newDiv.id = 'new';
newDiv.textContent = '我是新元素';
// 替換 oldDiv 為 newDiv
oldDiv.parentNode.replaceChild(newDiv, oldDiv);
// 複製一份(深層複製會把子節點一起帶走)
const clone = newDiv.cloneNode(true);
clone.id = 'clone';
document.body.appendChild(clone);
5️⃣ 操作註解節點(Comment)
// 建立註解節點
const comment = document.createComment('這是一段說明文字');
// 把註解插入到 <head> 之後
document.head.appendChild(comment);
// 之後若要移除
comment.parentNode.removeChild(comment);
常見陷阱與最佳實踐
| 陷阱 | 為什麼會發生 | 改善方式 |
|---|---|---|
使用 innerHTML 直接寫入大量內容 |
會觸發 HTML 解析、樣式重新計算,且易遭 XSS 攻擊。 | 優先使用 DOM 方法(createElement、appendChild)或 模板字串 + textContent。 |
遍歷 childNodes 時忘記過濾文字節點 |
文字節點的 nodeType 為 3,若直接操作可能產生錯誤。 |
使用 children 只取得元素,或在迴圈中檢查 node.nodeType === 1。 |
| 頻繁插入單一節點導致重排 | 每次 appendChild 都會觸發瀏覽器重新計算佈局。 |
批次操作:先放入 DocumentFragment,最後一次性插入。 |
直接修改 nodeValue 而非 textContent |
nodeValue 只適用於文字或註解節點,易忘記節點類型。 |
對元素使用 textContent/innerText,對文字節點則使用 nodeValue。 |
| 忘記釋放事件監聽器 | 移除節點時,若仍保留事件參考會造成記憶體泄漏。 | 在移除前使用 removeEventListener,或採用 事件委派。 |
最佳實踐小結
- 盡量使用
textContent取代innerHTML(除非真的需要解析 HTML)。 - 批次更新:
DocumentFragment、requestAnimationFrame或setTimeout可減少重排。 - 檢查
nodeType:操作前確保是正確的節點類型。 - 事件委派:把事件掛在父容器,減少大量
addEventListener。 - 保持語意:使用正確的 HTML 標籤(如
<ul>、<li>)配合 DOM 操作,讓 SEO 與可存取性更佳。
實際應用場景
| 場景 | 需求 | 相關節點操作 | 為何重要 |
|---|---|---|---|
| 動態清單(Todo List) | 新增、刪除、勾選項目 | createElement、appendChild、removeChild、classList.toggle |
需要即時更新 UI,且避免頻繁重排。 |
| 即時搜尋建議 | 使用者輸入時即時顯示建議列表 | DocumentFragment + cloneNode + textContent |
大量建議項目一次渲染,提高效能。 |
| 富文字編輯器 | 插入/刪除文字、圖片、註解 | 操作 Text、Element、Comment,使用 range、selection API |
必須精確控制文字節點與光標位置。 |
| SPA 路由切換 | 依路由載入不同的視圖 | 先清空容器 container.innerHTML = '',再用 appendChild 注入新節點 |
保持單頁應用的流暢度與記憶體管理。 |
| 自訂工具列(Toolbar) | 動態開關按鈕、顯示提示 | createElement('button')、setAttribute、addEventListener |
需要即時建立或移除 DOM,且保持語意正確。 |
總結
DOM 是前端開發的基礎建築,了解 樹狀結構、節點類型(Element、Text、Comment、DocumentFragment)以及 Node 介面的屬性與方法,能讓我們在操作頁面時更得心應手。
- 正確辨識
nodeType,避免把文字節點當作元素處理。 - 盡量使用 DOM 方法(
createElement、appendChild)取代直接寫入innerHTML,提升安全性與效能。 - 使用 DocumentFragment、事件委派 等技巧,減少重排與記憶體泄漏。
掌握這些核心概念後,你將能在 Todo List、即時搜尋、SPA 路由 等常見情境中,寫出乾淨、效能佳且易於維護的程式碼。未來若要深入學習更進階的瀏覽器 API(如 IntersectionObserver、ResizeObserver)或 Virtual DOM,這些基礎都會是最堅實的跳板。祝你在 JavaScript 的世界裡玩得開心、寫得順手!