☰
JavaScript 教程:使用 getElementsByTagName 统计嵌套 ul/li 树中每个节点的后代数量
2026/10/6 12:22:58 网站建设 项目流程
  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载

本篇文章基于 Modern JavaScript Tutorial(en.javascript.info)的「DOM 修改」章节中一个高价值实战任务(8-tree-count),完整还原问题场景与官方解法:给定一棵由嵌套ul/li构成的树,编写代码在每个<li>末尾追加其后代<li>的数量(叶子节点跳过)。读完本文你将掌握getElementsByTagName的集合语义、firstChild.data直接修改文本节点的技巧,以及如何用最简单的 DOM API 完成树形统计。

任务目标:为树的每个节点标注后代数量

任务要求十分明确:

  • 页面中存在一棵由嵌套的ul/li组织起来的树(source.view/index.html);
  • 为每个<li>追加文本,内容为它的后代<li>的数量;
  • 跳过叶子节点(即没有子<li>的节点)。

期望效果如下(solution.view/index.html 展示了运行结果):

  • Animals(含Mammals、Other两个分支及其全部子项)
  • Mammals(含 4 个子项:Cows、Donkeys、Dogs、Tigers)
  • Fishes(含Aquarium、Sea两个分支)
  • ……以此类推,最底层的Cows、Donkeys等叶子节点不追加任何内容。

初始 HTML 结构

source.view/index.html 给出了任务的初始骨架,树形结构如下(已省略重复的Sea分支细节):

<!DOCTYPE HTML> <html> <body> <ul> <li>Animals <ul> <li>Mammals <ul> <li>Cows</li> <li>Donkeys</li> <li>Dogs</li> <li>Tigers</li> </ul> </li> <li>Other <ul> <li>Snakes</li> <li>Birds</li> <li>Lizards</li> </ul> </li> </ul> </li> <li>Fishes <ul> <li>Aquarium <ul> <li>Guppy</li> <li>Angelfish</li> </ul> </li> <li>Sea <ul> <li>Sea trout</li> </ul> </li> </ul> </li> </ul> <script> // ... your code ... </script> </body> </html>

注意这里的一个结构细节:每个非叶子<li>的第一个子节点是文本节点(如Animals、Mammals),随后才是子<ul>。这个文本节点的存在正是解法能直接“原地追加文本”的关键。

官方解法:两行核心逻辑

官方解法 给出的提示是:“要往每个<li>追加文本,我们可以直接修改文本节点data”。完整实现位于 solution.view/index.html:

let lis = document.getElementsByTagName('li'); for (let li of lis) { // 统计当前 <li> 下方的所有 <li> let descendantsCount = li.getElementsByTagName('li').length; if (!descendantsCount) continue; // 直接追加到文本节点(在文本后面拼上 [数量]) li.firstChild.data += ' [' + descendantsCount + ']'; }

运行后页面呈现为:

  • Animals [5](其下共有 5 个<li>)
  • Mammals [4](Cows、Donkeys、Dogs、Tigers)
  • Other [3](Snakes、Birds、Lizards)
  • Fishes [4](其下共有 4 个<li>)
  • Aquarium [2](Guppy、Angelfish)
  • Sea [1](Sea trout)
  • 最底层的Cows、Donkeys等叶子节点原样不动。

关键 API 拆解

1.document.getElementsByTagName('li'):整棵树的所有<li>

getElementsByTagName是 DOM 搜索 API 中的经典方法,在教程的「搜索元素」章节中有明确说明:

  • elem.getElementsByTagName(tag)返回给定标签的元素集合;
  • tag参数还可以传"*"表示“任意标签”;
  • 方法名中带有复数形式的Elements,因为返回的是一个集合(HTMLCollection),这与返回单个元素的getElementById形成对照——这也是教程特别提醒过的易错点:getElementById没有字母"s",而getElementsByTagName有;
  • 集合中的元素可以通过索引访问,例如document.getElementsByTagName('input')[0],也可以通过for...of直接迭代。

在第一行代码中,document.getElementsByTagName('li')收集的是文档范围内全部<li>,因此for...of循环会覆盖外层节点和所有深层嵌套节点。

2.li.getElementsByTagName('li'):子树内的后代<li>

解法最关键的一步是在元素实例上再次调用getElementsByTagName('li'):它不再搜索整个文档,而是只搜索以当前<li>为根的子树,返回其中所有的后代<li>(不包含当前节点自身)。因此descendantsCount恰好就是“当前节点的后代数量”。

由于嵌套的<li>只会以“祖先后代”的方式存在(树中不存在兄弟<li>互相包含的情况),这里无需递归、无需去重——每个后代<li>都唯一地归属于某个祖先分支,数量统计天然正确。

3.li.firstChild.data += ' [...]':直接操作文本节点

这是官方解法强调的“锦上添花”技巧。在 DOM 中,<li>Animals<ul>...</ul></li>这样的写法,其子节点依次为:

  1. 文本节点"Animals"(即li.firstChild);
  2. 元素节点<ul>(即li.lastChild)。

文本节点的内容存储在.data属性中。所以li.firstChild.data += ' [5]'等价于“把' [5]'追加到文本末尾”,不会破坏原有文本、也不会误删子<ul>。整段操作没有新建任何元素、没有插入节点,仅仅是修改既有文本节点,因此即使getElementsByTagName返回的是实时(live)集合,循环遍历的集合结构也不会因修改而发生变化。

// 以 <li>Animals ...</li> 为例: li.firstChild // 文本节点 "Animals" li.firstChild.data // "Animals" li.firstChild.data += ' [5]'; // 变为 "Animals [5]"

叶子节点为何被跳过

if (!descendantsCount) continue;

叶子节点(如Cows、Guppy)内部没有任何<li>,li.getElementsByTagName('li').length返回0,条件!0为真,直接continue跳过。这一行同时承担了两件事:既过滤了叶子节点,又保证了文本格式的统一(只有有后代的节点才追加[N])。

完整可运行代码

将以下脚本放入 source.view/index.html 的<script>标签中,即可在浏览器中直接验证(最终效果见 solution.view/index.html):

<script> let lis = document.getElementsByTagName('li'); for (let li of lis) { let descendantsCount = li.getElementsByTagName('li').length; if (!descendantsCount) continue; li.firstChild.data += ' [' + descendantsCount + ']'; } </script>

解法背后的 DOM 修改知识体系

本任务出自教程的「修改文档(Modifying the document)」章节,该章节系统讲解了 DOM 的创建、插入、删除与克隆方法,与本任务相关的核心知识点包括:

  • 节点创建:document.createElement(tag)创建元素节点、document.createTextNode(text)创建文本节点(本任务没有新建节点,走的是“改”而非“建”的路线);
  • 插入方法:node.append(...)、node.prepend(...)、node.before(...)、node.after(...)、node.replaceWith(...)——如果任务要求的是“追加一个带计数的子元素”而非“在文本后拼接”,这些方法就是首选;
  • 文本插入安全性:append等方法插入字符串时按纯文本处理(自动转义<、>),不会解析为 HTML;如需按 HTML 解析则用elem.insertAdjacentHTML(where, html);
  • 节点删除:node.remove()。

理解这些背景有助于判断:本题选择直接修改firstChild.data,是因为只需要改文本、不涉及结构变更,这是最高效、最不容易出错的做法;而一旦需要新增元素节点(例如在每个<li>里再插入一个计数徽章),则应改用createElement+append的组合。

常见实现误区

  1. 误用innerHTML整体重写:如li.innerHTML = li.innerHTML + ' [5]'会把子<ul>一并覆盖或导致结构损坏,且会触发不必要的重解析,务必避免;
  2. 统计范围错误:只对顶层<li>调用getElementsByTagName('li')无法得到每个节点的独立计数;必须在每一个<li>自身上调用,才能获得其子树范围;
  3. 未跳过叶子节点:漏写if (!descendantsCount) continue;会导致所有叶子<li>被追加成[0],与题目要求不符;
  4. 误改元素节点:firstChild取到的是文本节点才能用.data;若某个<li>以子元素开头(如<li><span>...</span></li>),则需要改用li.firstElementChild或先定位到对应文本节点。

小结

知识点本任务中的应用
document.getElementsByTagName('li')获取文档内全部<li>,用于外层遍历
li.getElementsByTagName('li')获取当前节点子树内的后代<li>,用于计数
length属性集合元素个数,即后代数量;为0说明是叶子节点
firstChild.data += ...向既有文本节点追加文本,保持结构不变

核心代码仅 4 行,却完整覆盖了“集合搜索 + 子树统计 + 文本节点修改 + 条件跳过”四个要点,是理解 DOM 搜索 API 与节点修改的最佳入门实战。完整题目与解法可在仓库中直接查看:任务描述、官方解法说明、可运行示例。

  • 文档/教程
  • 前端

【免费下载链接】en.javascript.info

Modern JavaScript Tutorial

项目地址:https://gitcode.com/gh_mirrors/en/en.javascript.info
点击查看免费下载
上一篇:163MusicLyrics:免费开源歌词提取工具终极指南,轻松获取网易云与QQ音乐歌词
下一篇:Umi-OCR:开启你的离线文字识别新纪元

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询