DOM(文档对象模型)

DOM 的结构、节点类型、遍历与操作方式,以及事件委托机制。

#tech / dev / frontend #resource / browser #type / concept #status / growing

[!info] related notes

DOM(文档对象模型)

定义

DOM(Document Object Model)是浏览器将 HTML 文档解析后生成的树形数据结构。JavaScript 通过 DOM API 来读取、修改页面内容、结构和样式。

DOM 树结构

HTML 文档被解析为一棵节点树:

<html>
  <head>
    <title>页面标题</title>
  </head>
  <body>
    <h1>标题</h1>
    <p class="intro">一段文字</p>
  </body>
</html>

对应的 DOM 树:Document → html → {head → title → "页面标题", body → {h1 → "标题", p.intro → "一段文字"}}

节点类型

类型nodeType示例
Element1<div>, <p>
Text3文字内容
Comment8<!-- 注释 -->
Document9document
DocumentFragment11文档片段
Attr2class="card"

获取元素

// 推荐:现代 API
document.querySelector('.card')         // 第一个匹配
document.querySelectorAll('.card')      // 所有匹配(NodeList)

// 传统 API(更快,但灵活性低)
document.getElementById('app')
document.getElementsByClassName('card')  // HTMLCollection(动态)
document.getElementsByTagName('div')
document.getElementsByName('username')

querySelectorAll 返回静态 NodeList,不会随 DOM 变化;getElementsByClassName 返回动态 HTMLCollection。

遍历 DOM

const el = document.querySelector('.card')

// 父节点
el.parentNode          // 父节点(可能是 Text 节点)
el.parentElement       // 父元素节点

// 子节点
el.childNodes          // 所有子节点(包括 Text、Comment)
el.children            // 只有元素子节点
el.firstChild          // 第一个子节点
el.firstElementChild   // 第一个元素子节点
el.lastChild           // 最后一个子节点

// 兄弟节点
el.nextSibling
el.nextElementSibling
el.previousSibling
el.previousElementSibling

修改 DOM

const div = document.createElement('div')
div.textContent = '新内容'
div.className = 'card'

// 插入
parent.appendChild(child)
parent.insertBefore(newNode, referenceNode)
parent.append(child1, child2)         // 可追加多个,可追加字符串
parent.prepend(child)
parent.after(sibling)
parent.before(sibling)

// 删除
parent.removeChild(child)
child.remove()                        // 更简洁

// 替换
parent.replaceChild(newChild, oldChild)

// 修改属性
el.setAttribute('id', 'app')
el.getAttribute('id')
el.removeAttribute('id')
el.dataset.userId = '123'             // data-user-id="123"

// 修改样式
el.style.color = 'red'
el.classList.add('active')
el.classList.remove('active')
el.classList.toggle('active')
el.classList.contains('active')

DocumentFragment

DocumentFragment 是轻量的文档片段,不会触发重排,适合批量插入。

const fragment = document.createDocumentFragment()

for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li')
  li.textContent = `Item ${i}`
  fragment.appendChild(li)
}

// 一次性插入,只触发一次重排
ul.appendChild(fragment)

事件委托

利用事件冒泡机制,将事件监听器绑定在父元素上,通过 event.target 判断实际触发的子元素。

// 不推荐:给每个按钮都绑定事件
document.querySelectorAll('.btn').forEach(btn => {
  btn.addEventListener('click', handleClick)
})

// 推荐:事件委托
document.querySelector('.btn-container').addEventListener('click', (e) => {
  if (e.target.matches('.btn')) {
    handleClick(e)
  }
})

优势:

  • 减少事件监听器数量
  • 动态添加的子元素自动被处理
  • 内存占用更少

详见 event-bubbling-and-capturing

innerHTML vs textContent

el.innerHTML = '<b>粗体</b>'   // 解析 HTML,有 XSS 风险
el.textContent = '<b>粗体</b>' // 纯文本,安全
el.innerText = '内容'          // 受 CSS 影响(display: none 的不返回)

textContent 性能更好且更安全,优先使用。

边界

  • DOM 操作是昂贵的,频繁操作会触发 reflow/repaint
  • 批量操作时使用 DocumentFragment 或 innerHTML 一次性写入
  • querySelectorAll 返回的 NodeList 不是数组,需要用 Array.from() 或展开运算符转数组
  • DOM 事件监听器在元素移除后不会自动销毁(可能内存泄漏),需要手动 removeEventListener 或使用 AbortController
  • document.readyState 可以检查 DOM 是否加载完成

一句话记忆法

DOM 是浏览器把 HTML 解析成的树结构;用 querySelector 获取节点,用 createElement/appendChild 操作结构,用事件委托减少监听器。

创建于 2026/5/27 更新于 2026/5/27