原文链接:https://javascript.info/searching-elements-dom,translate with ❤️ by zhangbao.
对于相互接近的元素,使用 DOM 提供的遍历属性,操作起来非常方便。那么当我们想要获取页面上的任意一个元素时,该怎么做呢?
DOM 也有提供这样的查找方法。
document.getElementById 或直接使用 id
如果一个元素具有 id 特性值,那么对应就存在一个同名全局变量。
我们可以用这个变量访问元素:
<div id="elem"><div id="elem-content">Element</div></div><script>alert(elem); // 这里的 elem 就代表 <div id="elem"> 元素alert(window.elem); // window.elem 等同于 elem// 想要获取 id 是 elem-content 的元素// 必须使用 [] 的形式,因为 elem-content 不是一个有效的标识符alert(window['elem-content']);</script>
但这种方式也存在一些潜在问题,比如被我们一不小心的覆盖了。
<div id="elem"></div><script>let elem = 5;alert(elem); // 5</script>
这个行为在规范中描述,只是为了向后兼容。而且我们已经看到,这也可能带来一些诸如变量覆盖,不能清除判断一个变量表示的是哪个 DOM 节点的问题。
所以,最好的选择是使用 document.getElementById(id) 方法,手动获取 DOM 元素。
例如:
<div id="elem"><div id="elem-content">Element</div></div><script>let elem = document.getElementById('elem');elem.style.background = 'red';</script>
在本教程中,我们通常使用 id 直接引用元素,目的只是为了保证代码尽量简短而已。在真实的开发环境下,还是推荐使用 document.getElementById 方法。
id 值是唯一的
在一个文档中,一个 id`` 值必定在这个文档中是唯一的。
如果一个文档中存在多个 id
值一样的元素,那么使用 getElementById方法查询的结果将是不可预料的。浏览器可能返回它们中的任意一个,所以千万要确保 id 值的唯一性。
getElementById
** 仅是 ****document**对象上的方法不存在 anyNode.getElementById
方法,getElementById只能在 docuemnt`` 上使用——它查找文档范围内匹配给定 id 名的元素。
getElementsBy*
除了 getElementById,还提供了其他查找节点的方法:
elem.getElementsByTagName(tag): 返回指定标签的元素集合。tag可以是“*”也可以是“任意一个标签名”。
// 获得文档中所有的 divlet divs = document.getElementsByTagName('div');
这个方法也可以在 DOM 元素上调用。例如,查找表格里的所有 input:
<table id="table"><tr><td>Your age:</td><td><label><input type="radio" name="age" value="young" checked> less than 18</label><label><input type="radio" name="age" value="mature"> from 18 to 50</label><label><input type="radio" name="age" value="senior"> more than 60</label></td></tr></table><script>let inputs = table.getElementsByTagName('input');for (let input of inputs) {alert( input.value + ': ' + input.checked );}</script>
不要忘记字符 “s”``!
开发者经常忘记字符 “s”
。也就是会错误调用 getElementByTagName 而不是 getElementsByTagName。字符 “s”
没有出现在 getElementById中,是因为这个方法返回的始终是单个元素。但是 getElementsByTagName返回的是元素集合,因此才有 "s"字符在其中。
返回的是集合,而不是单个元素!
另一个普遍的新手错误是写成这样:
// 这是错误的document.getElementsByTagName('input').value = 5;这是行不通的,因为它获得的是一组输入框的集合,这是在给集合赋值,而不是集合内部的元素节点。
我们应该迭代集合或通过索引获取元素,然后赋值,像下面这样:
// 这样写 OK (如果这里有 input 的话)document.getElementsByTagName('input')[0].value = 5;
还有两个一类但较少使用的方法:
elem.getElementsByClassName(className):返回给定类名下的元素集合。document.getElementsByName:返回给定name特性值的元素集合(很少使用,这里提到只是为了教程的完整性)。
例如:
<form name="my-form"><div class="article">文章</div><div class="article--long">长文章</div></form><script>// 获得包含给定 name 特性值的元素let form = document.getElementByName('my-form')[0];// 在 form 下寻找包含给定类名的元素集合let articles = form.getElementsByClassName('article');alert(articles.length); // 2。发现 2 个包含类名“article”的元素</script>
下面迎来重磅选手 querySelectorAll。
querySelectorAll
调用 elem.querySelectorAll(css) 返回 elem 中匹配指定 CSS 选择器的所有元素组成的集合。这是最常使用的一个强大方法。
举个例子:我们查找由 ul 下最后一个 li 孩子元素组成的集合。
<ul><li>The</li><li>test</li></ul><ul><li>has</li><li>passed</li></ul><script>let elements = document.querySelectorAll('ul li:last-child');for (let elem of elements) {alert(elem.innerHTML); // "test" "passed"}</script>
这个方法非常强大,因为可以使用任何 CSS 选择器。
也支持伪类
像
:hover和:active这样的伪类选择器,也受到支持。例如,document.querySelectorAll(':hover')会返回当前所有处于 hover 状态的元素组成的集合(按照嵌套嵌套顺序,从最外层的 到最里层的嵌套元素)。
querySelector
elem.querySelector(css) 返回匹配给定 CSS 选择器的第一个元素。
也就是说,elem.querySelector(css) 的结果等同于 elem.querySelectorAll(css)[0]。如果只查找单个元素的话,前者比后者要快而且写法简短。
matches
之前的方法都是用来查找 DOM 节点的。
而 elem.matches(css) 则是检查元素 elem 是否匹配给定的 CSS 选择器,返回 true 或 false。
在我们需要遍历元素集合,过滤出我们感兴趣的元素时,这个方法用起来还是很顺手的。
例如:
<a href="http://example.com/file.zip">...</a><a href="http://ya.ru">...</a><script>// 找出 document.body.children 中匹配 a[href$="zip"] 的元素for (let elem of document.body.children) {if (elem.matches('a[href$="zip"]')) {alert("存档参考: " + elem.href );}}</script>
closest
所以元素的上属元素都称为祖先元素。也就是说,祖先就是父级、父级的父级……一级一级的父级及元素本身组成了一个祖先链。
elem.closest(css) 查找并返回最近的匹配给定的 CSS 选择器的祖先元素, elem 元素本身也在匹配之列。
也就是说,closest 方法从自身开始一路往上检查每一个父级元素,如果匹配了给定的选择器,就停止查找,返回该元素。
例如:
<h1>内容</h1><div class="contents"><ul class="book"><li class="chapter">第一章</li><li class="chapter">第二章</li></ul></div><script>let chapter = document.querySelector('.chapter'); // LIalert(chapter.closest('.book')); // ULalert(chapter.closest('.contents')); // DIValert(chapter.closest('h1')); // null (因为 h1 不是祖先元素)</script>
实时集合
所有的 "getElementsBy*" 的方法返回的都是实时集合。这个集合总是能实时反映文档当前的元素状态,当元素改变时这个集合也会“自动更新”。
下面例子里,有两个脚本。
第一个脚本创建一个变量,引用了所有
<div>元素。现在divs的length值是1。第二个脚本在浏览器多解析一个
<div>后执行,divs的length值变成了2。
<div>第一个 div</div><script>let divs = document.getElementsByTagName('div');alert(divs.length); // 1</script><div>第二个 div</div><script>alert(divs.length); // 2</script>
与 getElementsBy* 不同的是,querySelectorAll 返回的是一个静态集合,就是一个固定的元素集合。如果把上面例子的方法换成 querySelectorAll,两个脚本输出结果都是 1:
<div>第一个 div</div><script>let divs = document.querySelectorAll('div');alert(divs.length); // 1</script><div>第二个 div</div><script>alert(divs.length); // 1</script>
现在我们能容易区分不同点了。静态集合即使在文档中新增了一个 <div> 后,仍是不变的。
在这里,我们使用分开的脚本来说明添加元素是如何影响集合的,但是任何 DOM 操作都会影响它们,很快我们就会看到。
总结
下面列举了查找 DOM 节点的 6 个主要方法:
| 方法 | 通过……查找 | 可以在 elem 上调用吗? | 实时? |
|---|---|---|---|
getElementById |
id |
- | ✔ |
getElementsByName |
name |
- | ✔ |
getElementsByTagName |
标签名或 "*" |
✔ | ✔ |
getElementsByClassName |
类名 | ✔ | ✔ |
querySelector |
CSS 选择器 | ✔ | - |
querySelectorAll |
CSS 选择器 | ✔ | - |
请注意,getElementById 和 getElementsByName 只能在 document 上调用。
其他方法都可以在元素上调用。比如 elem.querySelectorAll(...) 查找 elem 中匹配给定选择器的元素集合。
除此之外,还提供了:
elem.matches(css)检查elem是否匹配给定 CSS 选择器,返回true或false。elem.closest(css)查找匹配给定的 CSS 选择器的最近祖先元素,elem元素本身也在查找之列。
还要提到一个方法用来检查父子关系:
elemA.contains(elemB):检查elemA中是否包含/等于elemB。
IE 浏览器的兼容性
elem.matches(css)在 IE 中不支持,需要使用非标准方法msMatchesSelector替代,
elem.closest(css)方法 IE 不支持,
elemA.contains(elemB)IE 全面支持(仅不支持在document上使用),
练习题
问题
一、查找元素
有一个文档,包含表格和表单。
怎么查找?
id="age-table"的表格。表格里的所有
label元素(有 3 个)。表格中的第一个
td元素。name="search"的form。表单的第一个
input。表单的最后一个
input。
二、统计后代数量
有一个内嵌的 ul/li 树结构。
<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>
书写代码展示每一个 <li> 的内容:
里面的文本是什么(不需要输出子树)。
嵌套子元素
<li>的数量——所有后代,包括深层嵌套的后代。
答案
一、
// 1. id="age-table" 的表格let table = document.getElementById('age-table');// 或者document.querySelector('#age-table');// 2. 表格里的所有 label 元素(有 3 个)table.getElementsByTagName('label');//// 或者document.querySelectorAll('#age-table label');// 3. 表格中的第一个 td 元素table.rows[0].cells[0]// 或者table.getElementsByTagName('td')// 或者table.querySelector('td')// 4. name="search" 的 formlet form = document.getElementsByName('search')[0]// 或者document.querySelector('form[name="search"]')// 5. 表单的第一个 inputform.getElementsByTagName('input')[0]// 或者form.querySelector('input')// 6. 表单的最后一个 inputlet inputs = form.querySelectorAll('input') // 查找所有inputs[inputs.length-1] // 拿最后一个// 或者form.querySelector('input:last-child');
二、
for (let li of document.querySelectorAll('li')) {// 从文本节点获得标题let title = li.firstChild.data;title = title.trim(); // 删除结尾处的额外空格// 统计后代 li 元素的数量let count = li.getElementsByTagName('li').length;alert(title + ': ' + count);}
(完)
id 值是唯一的
getElementById
不要忘记字符 “s”``!
也支持伪类