- 教程
- 文档
【免费下载链接】thejsway
The JavaScript Way book
本篇技术指南以开源图书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() |
| 单个 | 按 ID | getElementById() |
| 单个(第一个) | 不按 ID | querySelector() |
在绝大多数实际页面中,上述方法的性能差异微乎其微;当代码运行在超高频率的循环中、且对性能极为敏感时,才值得优先考虑前四种“定向”方法。
获取元素的信息
选中元素之后,下一步通常是读取它们携带的信息。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
相关推荐
HTML DOM选择器与遍历:如何高效获取元素和节点列表
HTML DOM选择器与遍历:如何高效获取元素和节点列表 掌握HTML DOM选择器和遍历技巧是前端开发的基础能力,能让你快速定位页面元素并进行操作。在vani
教程前端10个必读:DA项目中语义分割领域自适应经典论文解读
10个必读:DA项目中语义分割领域自适应经典论文解读 欢迎来到领域自适应(Domain Adaptation)的世界!🚀 在这个快速发展的机器学习分支中,语义
Strophe.js核心功能解析:BOSH与WebSocket双协议实现实时消息传输
Strophe.js核心功能解析:BOSH与WebSocket双协议实现实时消息传输 Strophe.js是一款强大的JavaScript库,专为实时消息传输设
即时通讯WebSocket通信
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考