三、遍历 DOM
对 DOM 的所有操作都是以document对象开始的。从它我们可以访问任何节点。
下图是描述对象间关系的图片,通过这些关系我们可以在 DOM 节点之间切换
3.1 document.documentElement 和 Body
最顶层的树节点可以直接作为 document 对象的属性来使用:
<html>=document.documentElement 顶层 DOM 节点<body>=document.body<head>=document.head
document.body 可以是个 null。
如果我们没写 body 标签,DOM 会自动创建,但是在以下情况下 body 可能是个 null
html1<html> 2 <head> 3 <script> 4 alert('From HEAD: ' + document.body) // null,这里目前还没有 <body> 5 </script> 6 </head> 7 8 <body> 9 <script> 10 alert('From BODY: ' + document.body) // HTMLBodyElement,现在存在了 11 </script> 12 </body> 13</html>
因为 DOM 节点是从上到下解析并形成的,如果脚本在 head 就开始运行了,浏览器还没读到 body,所以就不存在 document.body。
3.2 子节点:childNodes,firstChild,lastChild
- 子节点含义:对应的直系子元素。它们被完全被嵌套在给定的元素中。例如
head和body标签就是html元素的子元素。 - 子孙元素含义:嵌套在给定元素的所有元素,包括子元素以及子元素的子元素等。
如以下代码
html1<html> 2 <body> 3 <div>Begin</div> 4 5 <ul> 6 <li> 7 <b>Information</b> 8 </li> 9 </ul> 10 </body> 11</html>
body 节点的子节点是 div 节点跟 ul 节点(包括一些空白空格的文本节点)
body 的子孙元素是包含 div、ul、li、b 等子孙元素。
查子节点
我们可以用childNodes来列出所有子节点,包括文本节点。
html1<html> 2 <body> 3 <div>Begin</div> 4 5 <ul> 6 <li>Information</li> 7 </ul> 8 9 <div>End</div> 10 11 <script> 12 for (let i = 0; i < document.body.childNodes.length; i++) { 13 alert(document.body.childNodes[i]) // Text, DIV, Text, UL, ..., SCRIPT 14 } 15 </script> 16 ...more stuff... 17 </body> 18</html>
上面的代码会将 body 的所有子节点都打出来。
但不会打出...more stuff ...因为脚本运行时这个内容还没解析出来,所以浏览器读不到。
查第一个子节点:firstChild
查最后一个子节点:lastChild
查是否有子节点:node.hasChildNodes()
3.3 DOM 集合
childNodes 是一个伪数组,是部署了 iterator 接口的可迭代伪数组。
我们可以用 for..of 迭代它,但无法使用数组的方法。
不过我们可以用一些方法来让它使用数组的方法:
Array.from、[...childNodes]变成真数组- 用 call、apply 等方法
有几个注意点:
- DOM 集合是只读的,我们不能用 childNodes[i]=...的操作来替换一个子节点
- DOM 集合是实时的,它反映 DOM 的当前状态
- 不要用 for...in 对集合进行迭代,原因是 for..in 会遍历所有可枚举(enumerable)属性。DOM 集合中就有一些我们并不需要的这些属性会被 for..in 迭代到
3.4 兄弟节点和父节点
兄弟(Sibling)节点指的是有同一个父节点的节点。
html1<html> 2 <head> 3 ... 4 </head> 5 <body> 6 ... 7 </body> 8</html>
-
body 是 head 的下一个兄弟节点
-
head 是 body 的前一个兄弟节点
父节点:parentNode
上一个兄弟节点:previousSibling
下一个兄弟节点:nextSibling
javascript1console.log(document.body.parentNode.tagName) // HTML 2console.log(document.head.nextSibling.tagName) // BODY 3console.log(document.body.previousSibling.tagName) // HEAD
3.5 纯元素导航
childNodes 中有很多节点,包括文本节点、元素节点、注释节点等。
但是有时候我们只想要元素节点。
以下就是纯元素节点的导航
这些纯元素节点跟上面的节点访问类似,区别在于需要添加Element
- Children —— 纯元素节点的子节点
- firstElementChild —— 第一个子元素节点
- lastElementChild —— 最后一个子元素节点
- previousElementSibling —— 上一个兄弟元素节点
- nextElementSibling —— 下一个兄弟元素节点
- parentElement —— 父元素节点
parentElement 和 parentNode 的区别
从语义上来说 parentElement 是找父元素节点而 parentNode 是找父节点。
一般来说这两个是一样的,都是用于获取父节点。
不过出现这个 API 可能是由于document.documentElement的parentElement和parentNode不一致导致的
javascript1document.documentElement.parentNode // document 2document.documentElement.parentElement // null
因为 html 的父节点就是 document 对象
但是 document 并不是元素节点。
当我们希望从下到上遍历到 html 时,可能这个细节有用
javascript1while(elem === elem.parentElement){ //向上遍历,直到html顶层 2 ... 3}
3.6 特定的 DOM 属性
某些类型的 DOM 元素可能会提供特定于类型的其他属性。
比如表格(Table)
<Table>除了支持上面的基本属性,还额外支持以下属性:
- table.rows -
<tr>元素的集合 table.caption/tHead/tFoot- 引用表格的<caption>``<thead>``<tfoot>- table.tBodies -
<tbody>元素的集合
<thead>,<tfoot>,<tbody> 元素提供了 rows 属性:
tbody.rows— 表格内部<tr>元素的集合。
<tr>:
tr.cells— 在给定<tr>中的<td>和<th>单元格的集合。tr.sectionRowIndex— 给定的<tr>在封闭的<thead>/<tbody>/<tfoot>中的位置(索引)。tr.rowIndex— 在整个表格中<tr>的编号(包括表格的所有行)。
<td> 和 <th>:
td.cellIndex— 在封闭的<tr>中单元格的编号。
3.7 小结
给定一个 DOM 节点,我们可以使用导航(navigation)属性 访问其直接的邻居
- 对于所有节点:parentNode、childNodes、firstChild、lastChild、nextSibling、previousSibling
- 对于所有元素节点:parentElement、children、firstElementChild、lastElementChild、nextElementSibling、previousElementSibling
- 有一些特定的元素还能有额外的属性