☰
The JavaScript Way 实战:遍历与查询 DOM——掌握元素选择、CSS 选择器与信息获取
2026/9/27 10:42:00 网站建设 项目流程
  • 教程
  • 文档

【免费下载链接】thejsway

The JavaScript Way book

项目地址:https://gitcode.com/gh_mirrors/th/thejsway
点击查看免费下载

本篇技术指南以开源图书The JavaScript Way(仓库 manuscript/chapter14.md)中“Traverse the DOM”一章为骨架,系统讲解如何在浏览器中用 JavaScript 定位页面元素(按标签、类、ID、CSS 选择器)以及读取元素的 HTML 内容、文本内容、属性与类名。读者学完后,将能够在实际页面中快速、可靠地“抓到”任意 DOM 节点,并为后续的动态页面修改(见 chapter15.md)打下基础。

本书正文位于仓库manuscript/目录,由 mkdocs.yml 配置的 Material for MkDocs 构建,chapter14 归属于 “Create interactive web pages(创建交互式网页)” 章节板块,承接 chapter13.md 的 DOM 基础发现,是通往交互式网页开发的关键一环。

本章示例页面

全章示例围绕一张“世界七大奇迹”网页展开。页面包含古代与现代两组奇迹列表,并带有一个参考文献链接列表。后续所有选择与信息获取示例都基于这段 HTML:

<h1>Seven wonders of the world</h1> <p>Do you know the seven wonders of the world?</p> <div id="content"> <h2>Wonders from Antiquity</h2> <p>This list comes to us from ancient times.</p> <ul class="wonders" id="ancient"> <li class="exists">Great Pyramid of Giza</li> <li>Hanging Gardens of Babylon</li> <li>Lighthouse of Alexandria</li> <li>Statue of Zeus at Olympia</li> <li>Temple of Artemis at Ephesus</li> <li>Mausoleum at Halicarnassus</li> <li>Colossus of Rhodes</li> </ul> <h2>Modern wonders of the world</h2> <p>This list was decided by vote.</p> <ul class="wonders" id="new"> <li class="exists">Petra</li> <li class="exists">Great Wall of China</li> <li class="exists">Christ the Redeemer</li> <li class="exists">Machu Picchu</li> <li class="exists">Chichen Itza</li> <li class="exists">Colosseum</li> <li class="exists">Taj Mahal</li> </ul> <h2>References</h2> <ul> <li><a href="https://en.wikipedia.org/wiki/Seven_Wonders_of_the_Ancient_World">Seven Wonders of the Ancient World</a></li> <li><a href="https://en.wikipedia.org/wiki/New7Wonders_of_the_World">New Wonders of the World</a></li> </ul> </div>

选择元素

逐节点遍历的局限

在上一章 chapter13.md 中,我们学会了从document根节点出发,借助childNodes属性在页面结构中逐层向下移动。例如,要选中标题 “Wonders from Antiquity” 的h2元素,必须考虑元素之间的文本节点:它是body元素第 6 个子节点的第 2 个子节点,于是写出如下晦涩的代码:

// Show the "Wonders from Antiquity" h2 element console.log(document.body.childNodes[5].childNodes[1]);

这种逐节点遍历的方式既笨拙又极易出错:代码可读性差,一旦页面中插入新元素就必须同步更新索引。幸好,DOM 提供了一系列远为优雅的解决方案——选择方法(selection methods),它们让你能够直接“按需索取”元素,而无需关心节点在树中的精确位置。

按 HTML 标签选择:getElementsByTagName()

所有 DOM 元素都拥有getElementsByTagName()方法,它接收一个标签名作为参数,返回一个NodeList对象,其中包含所有匹配该标签的子元素。注意,搜索范围覆盖调用该方法的节点之下的全部后代元素,而不仅是直接子元素。

借助它,选中页面中第一个h2变得非常简单:

// Get all h2 elements into an array const titleElements = document.getElementsByTagName("h2"); console.log(titleElements[0]); // Show the first h2 console.log(titleElements.length); // 3 (total number of h2 elements in the page)

命名约定:给 DOM 元素节点相关的变量加上Element(或复数Elements)后缀是社区流行的命名习惯。本书全程沿用这一约定,例如上文用titleElements命名保存多个h2节点的变量,让读者一眼看出它装着“元素集合”。

按类名选择:getElementsByClassName()

类似的,getElementsByClassName()方法按类名返回元素的 NodeList。搜索同样覆盖调用节点的所有后代元素:

// Show all elements that have the class "exists" const existingElements = Array.from(document.getElementsByClassName("exists")); existingElements.forEach(element => { console.log(element); });

这里有一个关键细节:NodeList 对象并不是真正的 JavaScript 数组,因此并非所有数组方法都适用于它。例如在较旧的浏览器环境中,forEach()可能无法直接调用。为了对 NodeList 使用数组方法(如forEach()、map()、filter()),需要先用Array.from()将其转换为真正的数组——这正是上面代码第一行所做的工作。

按 ID 选择:getElementById()

document变量还提供getElementById()方法,在整个文档范围内返回具有指定 ID 的元素;如果找不到任何匹配元素,则返回null。

// Show element with the ID "new" console.log(document.getElementById("new"));

易错点:注意getElementById()中Element一词之后没有字母s,与其他两个getElements...方法(getElementsByTagName()、getElementsByClassName())不同。这一字之差是最常见的拼写错误来源。

通过 CSS 选择器选择:querySelectorAll() 与 querySelector()

对于更复杂的场景,可以使用CSS 选择器来访问 DOM 元素。先看一个“组合查询”需求:找出既属于古代奇迹、又依然存在的所有<li>元素。用前面学到的方法可以这样写:

// All "ancient" wonders that still exist console.log(document.getElementById("ancient").getElementsByClassName("exists").length); // 1

这种链式写法略显笨拙。为此 DOM 提供了两个基于 CSS 选择器的方法。

第一个是querySelectorAll(),它可以接受任意 CSS 选择器字符串,并返回所有匹配元素。上面的复杂查询瞬间变得简洁清晰:

// All paragraphs console.log(document.querySelectorAll("p").length); // 3 // All paragraphs inside the "content" ID block console.log(document.querySelectorAll("#content p").length); // 2 // All elements with the "exists" class console.log(document.querySelectorAll(".exists").length); // 8 // All "ancient" wonders that still exist console.log(document.querySelectorAll("#ancient > .exists").length); // 1

第二个是querySelector(),工作方式与querySelectorAll()一致,但只返回第一个匹配元素;若没有匹配项则返回null。

// Show the first paragraph console.log(document.querySelector("p"));

掌握 CSS 选择器语法是高效使用这两个方法的前提:除了本例用到的p(标签)、#content p(后代组合)、.exists(类)、#ancient > .exists(父子组合)之外,还包括属性选择器、伪类等更丰富的语法。从规范与实现角度看,querySelectorAll()返回的是静态 NodeList——在调用时一次性计算匹配结果,之后对 DOM 的增删不会影响该集合,这一点与getElementsByTagName()等返回动态(live)集合的方法形成鲜明对比,在“先查询、后修改页面”的代码中值得留意。

如何选择合适的选择方法

本章共介绍了五种选择方法,它们的适用场景各不相同。由于querySelectorAll()和querySelector()基于 CSS 选择器,理论上可以覆盖所有需求,但它们可能比其他方法执行得稍慢。因此,一般遵循以下经验法则:

需要获取的元素数量选择依据推荐方法
多个按标签getElementsByTagName()
多个按类名getElementsByClassName()
多个既不按类也不按标签querySelectorAll()
单个按 IDgetElementById()
单个(第一个)不按 IDquerySelector()

在绝大多数实际页面中,上述方法的性能差异微乎其微;当代码运行在超高频率的循环中、且对性能极为敏感时,才值得优先考虑前四种“定向”方法。

获取元素的信息

选中元素之后,下一步通常是读取它们携带的信息。DOM 为元素提供了读取 HTML 内容、文本内容、属性与类名的标准途径。

读取 HTML 内容:innerHTML

innerHTML属性返回 DOM 元素的HTML 内容——即元素内部所有标记与文本的原始字符串:

// The HTML content of the DOM element with ID "content" console.log(document.getElementById("content").innerHTML);

这个属性最初由微软引入,并不属于 W3C DOM 规范,但如今已被所有主流浏览器普遍支持,成为事实标准。有趣的是,本书下一章 chapter15.md 还会用innerHTML做“写”操作——比如用innerHTML += '<li id="c">C</li>'向列表追加条目、或赋空字符串清空内容,这正是读取与写入双向能力的体现。

读取文本内容:textContent

textContent属性返回 DOM 元素的全部文本内容,且不包含任何 HTML 标记。与innerHTML相比,它剥离了所有标签,只留下纯文本:

// The textual content of the DOM element with ID "content" console.log(document.getElementById("content").textContent);

两者用途泾渭分明:需要以 HTML 字符串形式获得结构(例如做序列化或诊断)时用innerHTML;只需要“看得见”的文字(例如统计字数、拼接文案)时用textContent。

读取属性:getAttribute()、hasAttribute() 与直接属性访问

getAttribute()方法应用于某个 DOM 元素,返回指定属性的值:

// Show href attribute of the first link console.log(document.querySelector("a").getAttribute("href"));

此外,部分属性(如id、href、value)可以直接作为元素的属性(property)访问,写法更简洁:

// Show ID attribute of the first list console.log(document.querySelector("ul").id); // Show href attribute of the first link console.log(document.querySelector("a").href);

如果只想判断某个属性是否存在(而不是读取其值),可使用hasAttribute()方法,它返回布尔值:

if (document.querySelector("a").hasAttribute("target")) { console.log("The first link has a target attribute."); } else { console.log("The first link does not have a target attribute."); // Will be shown }

由于示例页面中的第一个链接没有target属性,这段代码会输出 else 分支中的提示。

读取类名:classList 与 contains()

一个 HTML 标签可以拥有多个类。classList属性返回该 DOM 元素的类列表,这是一个类数组对象(DOMTokenList),既支持按索引访问,也支持length长度属性:

// List of classes of the element identified by "ancient" const classes = document.getElementById("ancient").classList; console.log(classes.length); // 1 (since the element only has one class) console.log(classes[0]); // "wonders"

若要测试元素是否包含某个类,可在类列表上调用contains()方法,并传入待测试的类名:

if (document.getElementById("ancient").classList.contains("wonders")) { console.log("The element with ID 'ancient' has the class 'wonders'."); // Will be shown } else { console.log("The element with ID 'ancient' does not have the class 'wonders'."); }

因为id="ancient"的<ul>元素带有class="wonders",所以会输出前一个分支的结果。

以上只是 DOM 遍历 API 的一部分。classList其实还提供add()、remove()、toggle()等修改方法,Element接口上还有firstElementChild、lastElementChild、previousElementSibling等更丰富的导航属性,读者可在后续章节或 MDN 的Element文档中继续深挖。

本章要点速览

  • 与其逐节点遍历 DOM,不如使用选择方法快速定位一个或多个元素。
  • getElementsByTagName()、getElementsByClassName()分别按标签名、类名搜索,两者都返回列表(NodeList),可用Array.from()转换为数组;getElementById()按ID搜索,返回单个元素。
  • querySelectorAll()与querySelector()支持用CSS 选择器搜索:前者返回全部匹配项,后者只返回第一个匹配项。
  • innerHTML返回元素的HTML 内容,textContent返回不含任何 HTML 标记的文本内容。
  • getAttribute()与hasAttribute()用于访问元素的属性;classList属性及其contains()方法用于访问元素的类名。

动手实践

本章附有三组循序渐进的练习,全部基于浏览器环境:把示例 HTML 保存为本地网页,打开浏览器控制台(如 Chrome DevTools Console)逐行运行验证即可。

练习一:统计元素数量

以下 HTML 片段取自法国诗人 Paul Verlaine 的诗作Mon rêve familier:

<h1>Mon rêve familier</h1> <p>Je fais souvent ce rêve <span class="adjective">étrange</span> et <span class="adjective">pénétrant</span></p> <p>D'une <span>femme <span class="adjective">inconnue</span></span>, et que j'aime, et qui m'aime</p> <p>Et qui n'est, chaque fois, ni tout à fait la même</p> <p>Ni tout à fait une autre, et m'aime et me comprend.</p>

请补全countElements()函数——它接收一个 CSS 选择器作为参数,返回对应元素的数量:

// TODO: write the countElements() function here console.log(countElements("p")); // Should show 4 console.log(countElements(".adjective")); // Should show 3 console.log(countElements("p .adjective")); // Should show 3 console.log(countElements("p > .adjective")); // Should show 2

参考解答:一个可行实现是直接用querySelectorAll()配合length:

// Count elements matching a CSS selector const countElements = (selector) => document.querySelectorAll(selector).length;

验证思路:p匹配 4 个段落;.adjective匹配 3 个强调词;p .adjective是“段落后代中的强调词”,同样是 3 个(第三个段落里嵌套的inconnue也在段落之内);而p > .adjective要求强调词直接是段落的子元素,嵌套在span中的inconnue不满足条件,故结果为 2。

练习二:处理属性

以下是几种乐器的描述列表:

<h1>Some musical instruments</h1> <ul> <li id="clarinet" class="wind woodwind"> The <a href="https://en.wikipedia.org/wiki/Clarinet">clarinet</a> </li> <li id="saxophone" class="wind woodwind"> The <a href="https://en.wikipedia.org/wiki/Saxophone">saxophone</a> </li> <li id="trumpet" class="wind brass"> The <a href="https://en.wikipedia.org/wiki/Trumpet">trumpet</a> </li> <li id="violin" class="chordophone"> The <a href="https://en.wikipedia.org/wiki/Violin">violin</a> </li> </ul>

请编写一个包含linkInfo()函数的 JavaScript 程序,要求显示:

  • 页面上链接的总数。
  • 第一个与最后一个链接的href目标。

该函数必须在页面没有任何链接时也能正常工作。

然后在 HTML 列表末尾追加一件新乐器,并检查程序的新结果:

<li id="harpsichord"> The <a href="https://en.wikipedia.org/wiki/Harpsichord">harpsichord</a> </li>

参考解答:关键点在于先用querySelectorAll("a")拿到全部链接,再借助数组索引访问首尾元素;当length为 0 时跳过属性读取,避免在空集合上取索引:

// Show information about the page's links const linkInfo = () => { const links = Array.from(document.querySelectorAll("a")); console.log(`The page contains ${links.length} links.`); if (links.length > 0) { console.log(`First link: ${links[0].href}`); console.log(`Last link: ${links[links.length - 1].href}`); } }; linkInfo();

练习三:处理类名

继续改进上一个程序,增加一个has()函数:根据元素的 ID 判断它是否拥有某个类,输出true、false;如果找不到该元素,则输出错误提示。

// Show if an element has a class const has = (id, someClass) => { // TODO: write the function code }; has("saxophone", "woodwind"); // Should show true has("saxophone", "brass"); // Should show false has("trumpet", "brass"); // Should show true has("contrabass", "chordophone"); // Should show an error message

提示:用console.error()而非console.log()来在控制台显示错误信息。

参考解答:核心是先用getElementById()查找元素并处理null分支(这正是本章强调的“找不到返回 null”的实战应用),再用classList.contains()判断类名:

// Show if an element has a class const has = (id, someClass) => { const element = document.getElementById(id); if (element === null) { console.error(`No element has the id "${id}".`); return; } console.log(element.classList.contains(someClass)); };

逐条核对预期:saxophone的类为wind woodwind,包含woodwind(true)而不包含brass(false);trumpet的类为wind brass,包含brass(true);而contrabass在页面中根本不存在,getElementById()返回null,于是走错误分支输出提示。

在本地运行与验证

想跟随本书边读边练,有两种方式。其一是把文中示例保存为.html文件,直接用浏览器打开并配合开发者工具控制台逐段运行 JavaScript;这也是本书“零环境依赖”的推荐做法。其二是将整个仓库作为 MkDocs 站点在本地浏览:按 README.md 的说明,先安装 poetry,然后在仓库根目录依次执行:

poetry shell poetry install mkdocs serve

默认在http://localhost:8000提供书籍在线浏览,第 14 章对应manuscript/chapter14.md源文件,可随时对照正文、图片与练习进行学习。

  • 教程
  • 文档

【免费下载链接】thejsway

The JavaScript Way book

项目地址:https://gitcode.com/gh_mirrors/th/thejsway
点击查看免费下载

相关推荐

上一篇:全面解析R3nzSkin:5个高效安全使用英雄联盟换肤工具的最佳实践
下一篇:网盘直链下载助手终极指南:三步摆脱限速烦恼

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

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

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

立即咨询