LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

HTML DOM API 完全指南:从核心接口到高级实践

admin
2026年5月20日 17:40 本文热度 63

浏览器中的每个页面都是一棵由节点构成的树,这棵树的编程接口就是 DOM。无论你使用原生 JavaScript 还是 React、Vue 等现代框架,最终对页面内容的每一次增删改查,都离不开 HTML DOM API。然而,许多开发者对 DOM 的理解停留在“会用”层面——知道 querySelector 却分不清 HTMLCollection 与 NodeList 的动态性差异;习惯用 innerHTML 却忽略了 DocumentFragment 的性能优势;熟悉 addEventListener 却不了解 MutationObserver 的监听能力。

本文以 MDN 权威文档为基准,系统梳理 DOM 的本质、四层核心接口的继承关系、元素查询与集合类型、属性与特性的区别、事件委托、性能优化技巧以及 MutationObserver 与 Shadow DOM 等现代特性。通过清晰的逻辑、对比表格和实战代码,帮助你构建一套准确、完整的 DOM 知识体系。

一、DOM 的本质:文档的树形抽象


DOM(Document Object Model,文档对象模型)是浏览器将 HTML 或 XML 文档解析后生成的一个树形结构。它提供了编程接口,允许脚本动态访问和修改文档的内容、结构与样式。

每个 HTML 文档加载后,会产生一个 Document 对象作为树的根节点。树中的每个组成部分都称为一个“节点”(Node)。例如:

<html>  <head><title>示例</title></head>  <body>    <div>Hello</div>    <ul><li>项1</li></ul>  </body></html>

其 DOM 树结构(简化)如下:

Document└── html (Element)    ├── head (Element)    │   └── title (Element)    │       └── text (Text)    └── body (Element)        ├── div (Element)        │   └── text (Text)        └── ul (Element)            └── li (Element)                └── text (Text)

节点类型众多,日常开发中最常用的是 元素节点(Element) 和 文本节点(Text)

二、核心接口的继承层次与职责


理解 DOM 的关键在于理清 Node → Element → HTMLElement → 具体元素以及 Document 这四类接口的职责划分。

继承关系图

Node (抽象基类)├── Document          // 整个文档的入口├── Element           // 所有元素节点的基类│   ├── HTMLElement   // 所有 HTML 元素的基类│   │   ├── HTMLDivElement│   │   ├── HTMLInputElement│   │   └── ...(其他具体元素)│   └── SVGElement    // SVG 元素的基类└── Text              // 文本节点

1. Node —— 所有节点的始祖

Node 是最底层的抽象接口,任何类型的节点(元素、文本、注释、文档)都实现了它。它主要提供 节点树中通用的关系导航 和 基本操作

  • 关系属性:parentNode、childNodes、firstChild、lastChild、nextSibling、previousSibling。

  • 基本方法:appendChild()、removeChild()、cloneNode()、contains()。

// Node 示例const parent = node.parentNode;      // 父节点(可能是元素或文档)const children = node.childNodes;    // 所有子节点(含文本节点)

注意:childNodes 会包含文本节点(如换行符和空格),而不仅仅是元素节点。

2. Element —— 元素节点的专用接口
  • 属性操作:getAttribute()、setAttribute()、removeAttribute()。

  • 类名与样式:classList、style、className。

  • 元素间导航(忽略文本节点):children(返回 HTMLCollection)、firstElementChild、nextElementSibling。

// Element 示例element.setAttribute('data-id''123');element.classList.add('active');for (let child of element.children) { /* 仅遍历元素子节点 */ }

3. HTMLElement —— 所有 HTML 元素的共同父类

HTMLElement 继承自 Element,为所有 HTML 元素(<div>、<input>、<button> 等)提供了通用的属性和方法。具体的元素接口(如 HTMLInputElement)会进一步扩展自己特有的属性(如 value、checked)。

  • 通用属性:hidden、innerText、offsetHeight、offsetWidth、contentEditable、dir。

// HTMLElement 示例div.hidden = true;               // 隐藏元素console.log(div.offsetHeight);   // 元素可视高度input.value = '文本';            // 此属性属于 HTMLInputElement

4. Document —— 文档的总入口

Document 代表整个 HTML 文档,是 DOM 树的根节点(同时也是 window 对象的属性)。它提供文档级别的全局功能。

  • 文档信息:title、URL、cookie、referrer。

  • 创建节点:createElement()、createTextNode()、createDocumentFragment()。

  • 查询元素:getElementById()、querySelector()、querySelectorAll()。

  • 快捷属性:document.body、document.documentElement(<html>)、document.head。

// Document 示例document.title = '新标题';const div = document.createElement('div');const app = document.getElementById('app');

快速记忆

接口

一句话职责

典型使用场景

Node

通用节点关系

parentNode、childNodes、appendChild

Element

元素属性与子元素

getAttribute、classList、children

HTMLElement

HTML 元素通用行为

hidden、offsetHeight、innerText

Document

文档全局入口

createElement、getElementById、title


三、元素查询方法与集合类型


DOM 提供了多种查询元素的方法,但它们的返回值类型和行为差异常被忽略。

方法 / 属性

返回类型

动态性

包含内容

getElementById()

单个元素或 null

元素

getElementsByClassName()

HTMLCollection

动态

仅元素

getElementsByTagName()

HTMLCollection

动态

仅元素

getElementsByName()

NodeList(通常静态)

视浏览器而定

元素

querySelector()

单个元素或 null

元素

querySelectorAll()

NodeList

静态

元素

element.children

HTMLCollection

动态

仅元素

element.childNodes

NodeList

动态

所有节点

动态集合 vs 静态集合
  • 动态集合:HTMLCollection 以及 childNodes 返回的 NodeList 会随着 DOM 树的变化实时更新。如果在遍历动态集合的同时增删节点,可能导致无限循环或意外行为。

  • 静态集合:querySelectorAll() 返回的 NodeList 是方法调用时刻的快照,后续 DOM 变化不会影响它。

// 动态集合的典型陷阱const divs = document.getElementsByTagName('div'); // HTMLCollection 动态for (let i = 0; i < divs.length; i++) {    const newDiv = document.createElement('div');    document.body.appendChild(newDiv);    // divs.length 持续增长 → 可能演变为无限循环}
// 安全做法:使用静态集合const staticDivs = document.querySelectorAll('div');

遍历与转换
  • NodeList(现代浏览器)支持 forEach、keys、values、entries,但 不支持  map、reduce。如需使用数组方法,可转为真数组:Array.from(nodeList) 或 [...nodeList]。

  • HTMLCollection 没有 任何数组迭代方法,必须先转换。


四、创建、修改与删除元素


创建节点

const div = document.createElement('div');        // 元素节点const text = document.createTextNode('文本');     // 文本节点const fragment = document.createDocumentFragment(); // 文档片段

修改内容

// textContent vs innerHTMLelement.textContent = '纯文本';  // 不解析 HTML,天然防止 XSSelement.innerHTML = '<span>带标签的内容</span>'// 解析 HTML,有 XSS 风险

插入与移除

传统方法(兼容性好)

parent.appendChild(child);parent.insertBefore(newNode, refNode);parent.removeChild(child);parent.replaceChild(newNode, oldChild);

现代方法(简洁直观,IE 不支持)

parent.append(newNode1, newNode2);   // 末尾追加多个parent.prepend(newNode);             // 开头插入child.before(newNode);               // 在 child 之前插入child.after(newNode);                // 在 child 之后插入child.replaceWith(newNode);          // 替换自身child.remove();                      // 直接移除自身

五、Attribute 与 Property 的本质区别


这一概念经常令人困惑,但厘清后能避免大量 bug。

  • Attribute:写在 HTML 标签中的属性字符串,如 class="btn" data-id="5"。通过 getAttribute() / setAttribute() 操作。

  • Property:DOM 对象上的 JavaScript 属性,如 element.className 、element.dataset.id 。

对于标准属性(如 id、class、value),Attribute 与 Property 通常会同步,但以下情况需特别注意:

  • input.value 的 Property 改变后,Attribute 不会自动更新(但用户输入会同步 Property)。

  • 布尔属性(disabled、checked、readonly)的 Property 表现为 true/false,而 Attribute 为字符串或不存在。

最佳实践:优先使用 Property,除非你需要读取 HTML 中原始的 Attribute 值(例如通过 getAttribute 获取未曾改变的 value)。对于自定义数据,推荐使用 dataset。

// 推荐方式input.disabled = true;div.dataset.userId = '123';
// 而非 input.setAttribute('disabled''disabled');

六、事件处理与事件委托


标准事件监听

element.addEventListener('click', (event) => {    console.log(event.target);        // 实际触发事件的元素    console.log(event.currentTarget); // 绑定监听器的元素    event.preventDefault();           // 阻止默认行为(如链接跳转、表单提交)    event.stopPropagation();          // 阻止事件冒泡});

事件流顺序:捕获阶段 → 目标阶段 → 冒泡阶段。第三个参数可传入 true 或 { capture: true } 以在捕获阶段触发。

事件委托

利用事件冒泡机制,在父元素上统一监听子元素的事件。优点:

  • 减少事件监听器数量,提升性能。

  • 自动处理动态添加的子元素,无需重新绑定。

document.querySelector('.todo-list').addEventListener('click'(e) => {    const deleteBtn = e.target.closest('.delete-btn');    if (deleteBtn) {        deleteBtn.closest('.todo-item').remove();    }});

closest 方法向上查找匹配选择器的祖先元素,比直接判断 target 更加健壮。

七、DOM 遍历与关系导航

// 子节点(含文本) vs 子元素(不含文本)const allChildren = parent.childNodes;   // NodeList(动态)const elementChildren = parent.children// HTMLCollection(动态,仅元素)
// 兄弟元素(跳过文本节点)const nextElement = element.nextElementSibling;const prevElement = element.previousElementSibling;
// 父元素(一定是 Element,否则返回 null)const parentElement = element.parentElement;

八、性能优化:编写高效的 DOM 操作


DOM 操作是浏览器中最昂贵的操作之一,每次修改都可能触发 重排(reflow) 和 重绘(repaint)。遵循以下原则可显著提升性能。

1. 使用 DocumentFragment 批量插入

DocumentFragment 是一个存在于内存中的文档片段,将其作为临时容器批量添加子节点,最后一次性插入真实 DOM,将多次重排减少到一次。

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); // 只触发一次重排

2. 读写分离,避免布局抖动

交替读取布局属性(如 offsetHeight、getComputedStyle)和修改样式会导致浏览器强制同步布局。应先将所有读取操作集中,再统一写入。

// 性能差:读写交替elements.forEach(el => {    const h = el.offsetHeight// 读(强制布局)    el.style.height = h + 10;  // 写(使布局失效)});
// 优化:先读后写const heights = elements.map(el => el.offsetHeight);elements.forEach((el, i) => {    el.style.height = heights[i] + 10;});

3. 使用 classList 切换样式,避免逐个修改内联样式

// 避免el.style.width = '100px';el.style.height = '100px';el.style.background = 'red';
// 推荐el.classList.add('highlight-box');

4. 对高频事件使用防抖或节流

input.addEventListener('input'debounce(handleSearch, 300));window.addEventListener('scroll'throttle(handleScroll, 100));

九、高级 API:

MutationObserver 与 Shadow DOM


1. MutationObserver —— 高效监听 DOM 变化

传统的 MutationEvent 已废弃,MutationObserver 提供异步、批量的监听能力,性能更优。

const observer = new MutationObserver((mutations) => {    mutations.forEach(m => {        if (m.type === 'childList') {            console.log('子节点变化', m.addedNodes, m.removedNodes);        } else if (m.type === 'attributes') {            console.log(`属性变化: ${m.attributeName}`);        }    });});observer.observe(targetElement, {    childListtrue,   // 监听子节点增删    attributestrue,  // 监听属性变化    subtreetrue      // 监听所有后代节点});// 停止观察observer.disconnect();

典型应用场景:监听动态加载的评论区内容、自动保存编辑器修改、检测第三方脚本对 DOM 的非预期修改。

2. Shadow DOM —— 样式与 DOM 的封装

Shadow DOM 是 Web Components 的核心技术之一,允许将一棵独立的 DOM 树附加到常规元素上,实现样式隔离和 DOM 封装。

const host = document.querySelector('#widget');const shadow = host.attachShadow({ mode: 'open' });shadow.innerHTML = `    <style>p { color: purple; }</style>    <p>外部样式不会影响我,我也不会泄漏出去</p>`;

  • mode: 'open' → 外部可通过 host.shadowRoot 访问内部 DOM。

  • mode: 'closed' → 外部无法访问,增强封装性。


总结


HTML DOM API 是前端开发无法绕过的底层技术。掌握它的关键在于:

  1. 理清接口层次:Node → Element → HTMLElement → 具体元素,以及独立的 Document。每个接口有明确的职责范围——Node 管节点关系,Element 管属性与子元素,HTMLElement 提供 HTML 特有属性,Document 是全局入口。

  2. 区分集合类型:HTMLCollection 动态且仅含元素;NodeList 可能静态(querySelectorAll)或动态(childNodes)。遍历时注意动态集合的陷阱。

  3. 正确选择 API:Attribute vs Property,textContent vs innerHTM`,children vs childNodes。

  4. 性能意识:使用 DocumentFragment、读写分离、事件委托、防抖节流。

  5. 拥抱现代能力:MutationObserver 监听 DOM 变化,Shadow DOM 实现样式隔离。

无论你使用何种框架,扎实的 DOM 基础都能让你在调试性能瓶颈、实现复杂交互时游刃有余。希望本文能帮助你系统性地建立这份功底。


阅读原文:https://mp.weixin.qq.com/s/N7G5DLtoZIrpE91vfIf6FQ


该文章在 2026/5/20 17:40:52 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-1  粤公网安备44030602007207号