导读:本期聚焦于小伙伴创作的《JavaScript DOM 遍历有哪些方法?节点树和元素树遍历怎么选?》,敬请观看详情。操作页面结构时,常分不清 childNodes 与 children 的差异,导致遍历结果混入文本节点。节点树包含元素、文本、注释等所有节点,元素树仅含标签节点。本文从底层结构说明两类树的组成,对比递归、TreeWalker、querySelectorAll 等遍历手段的适用场景,并给出避免空白文本干扰的实用写法,帮助在动态渲染与数据抽取中准确选取目标节点。

在浏览器环境中,每一个 HTML 文档都会被解析为一棵由节点构成的树状结构。JavaScript 提供了丰富的接口来访问这棵树,但很多人在写代码时并没有意识到自己操作的是“节点树”还是“元素树”,从而在处理列表、表格等内容时出现意料之外的空值或换行符。理解这两类树的区别,是写好 DOM 遍历逻辑的前提。

JavaScript DOM 遍历有哪些方法?节点树和元素树遍历怎么选?

一、节点树与元素树的根本区别

节点树(Node Tree)是文档最完整的表达形式,它的每一个单位都是 Node 类型的实例。除了我们熟悉的标签节点(元素节点,nodeType 为 1),节点树还包含文本节点(nodeType 为 3)、注释节点(nodeType 为 8)、文档节点(nodeType 为 9)等。当你通过 childNodes 属性获取子节点时,拿到的是节点树视角下的全部直接子级。

元素树(Element Tree)则可以理解为节点树的一个“子集投影”,它只保留元素节点,忽略文本与注释。通过 children 属性获取的就是元素树下的子元素集合,类型为 HTMLCollection。在绝大多数业务开发中,我们关心的只是标签之间的嵌套与内容,因此元素树更符合直觉,也能避免空格与换行被当作文本节点处理。

下面用一段简单的 HTML 来说明两者的差异:

<ul id="list">
  <li>第一项</li>
  <li>第二项</li>
</ul>

如果在这个结构上分别读取 childNodeschildren,会得到不同长度的集合。前者包含三个子节点:两个 li 元素之间还有一个由换行和缩进组成的文本节点;后者只包含两个 li 元素。

二、基础遍历属性与方法

在节点树中,常用的导航属性包括 parentNodechildNodesfirstChildlastChildnextSiblingpreviousSibling。这些属性会严格按照节点树返回结果,文本与注释节点也在其列。例如使用 firstChild 时,如果标签开头有空格,拿到的可能是文本节点而非元素。

对应地,元素树提供了一套更“干净”的属性:parentElementchildrenfirstElementChildlastElementChildnextElementSiblingpreviousElementSibling。它们只跨越元素节点,在编写组件或提取数据时更安全。下面的代码展示了如何用元素树属性遍历一个容器中的所有直接子标签:

const container = document.getElementById('list');
let el = container.firstElementChild;
while (el) {
  console.log(el.tagName, el.textContent);
  el = el.nextElementSibling;
}

这种写法不会受到空白文本节点的干扰。如果你的运行环境较老,不支持 firstElementChild 等属性,也可以通过判断 nodeType === 1 来手动过滤 childNodes 中的非元素节点,但代码会更冗长。

三、递归与深度优先遍历

当结构嵌套较深时,仅靠兄弟节点导航不够用,通常需要递归访问整棵子树。对于节点树递归,要小心文本节点没有 childNodes 之外的子结构;对于元素树递归,则直接遍历 children 即可。下面给出一个通用的元素树深度优先遍历函数:

function walkElements(root, callback) {
  // root 必须是元素节点
  callback(root);
  const kids = root.children;
  for (let i = 0; i < kids.length; i++) {
    walkElements(kids[i], callback);
  }
}

// 使用示例:打印所有元素的标签名
walkElements(document.body, function(node) {
  console.log(node.tagName);
});

这种递归方式逻辑清晰,适合需要对每个元素做统一处理的场景,例如收集所有带特定属性的输入框。不过当 DOM 规模非常大时,递归可能带来调用栈压力,此时可以改为基于栈或队列的迭代写法,将待处理元素存入数组依次弹出,避免深层嵌套导致的栈溢出。

如果只关心节点树,也可以写出类似的递归,但要先过滤节点类型。例如在抽取网页纯文本时,遇到文本节点就追加内容,遇到元素节点就继续向下,这样既能保留换行结构,又不会误把注释输出到结果里。

四、使用 TreeWalker 与 querySelectorAll

浏览器原生提供了 TreeWalker 接口,用于在节点树中按条件高效遍历。通过 document.createTreeWalker 可以指定根节点、遍历的节点类型以及过滤函数。它比手动递归更节省内存,也更容易控制只访问元素或只访问文本。

// 仅遍历元素节点
const walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  null
);
let current = walker.nextNode();
while (current) {
  console.log(current.tagName);
  current = walker.nextNode();
}

与之相比,querySelectorAll 虽然不属于“一步步走”的遍历,但它在底层同样基于文档树搜索,能以 CSS 选择器一次性拿到元素树中的匹配集合。对于已知结构的查询,如获取所有 input 或某个 class 下的 li,它更简洁且可读性高。不过它返回的是静态 NodeList,不会随 DOM 变动自动更新,这一点与 children 这类实时集合不同。

方式所属树实时性适用场景
children / firstElementChild元素树实时简单层级导航
childNodes / firstChild节点树实时需处理文本或注释
TreeWalker节点树(可筛选)实时大型结构按类型遍历
querySelectorAll元素树静态选择器批量获取

五、实际开发中的选择建议

在普通业务里,如果只想操作标签,优先使用元素树相关属性,能少写很多过滤逻辑。当需要从用户输入的富文本中抽取纯文字,或分析包含注释的模板时,再回到节点树,用 nodeType 做判断。对于动态列表渲染后的遍历,结合 children 与索引访问通常比递归更直接。

需要补充的是,现代框架大多屏蔽了手动遍历,但在写底层工具、浏览器插件或爬虫脚本时,DOM 遍历依旧是核心能力。明确节点树与元素树的边界,不仅能减少 bug,还能让代码意图更清晰,便于后续维护。

JavaScriptDOM_traversalnode_tree修改时间:2026-08-08 02:54:37

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。