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

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

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

浏览器中的每个页面都是一棵由节点构成的树,这棵树的编程接口就是 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, {    childList: true,   // 监听子节点增删    attributes: true,  // 监听属性变化    subtree: true      // 监听所有后代节点});// 停止观察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号