DOM 允许我们对元素及其内容执行任何操作,但首先我们需要找到相应的 DOM 对象。
所有对 DOM 的操作都从 document
对象开始。这是 DOM 的主要“入口点”。我们可以从它访问任何节点。
下面是一幅允许在 DOM 节点之间穿梭的链接图片
让我们更详细地讨论它们。
顶部:documentElement和 body
最顶层的树节点可以直接作为 document
属性使用
<html>
=document.documentElement
- 最顶层的文档节点是
document.documentElement
。这是<html>
标记的 DOM 节点。 <body>
=document.body
- 另一个广泛使用的 DOM 节点是
<body>
元素 –document.body
。 <head>
=document.head
<head>
标记可用作document.head
。
document.body
可能为 null
脚本无法访问在运行时不存在的元素。
尤其是,如果脚本在 <head>
中,则 document.body
不可使用,因为浏览器尚未读取它。
因此,在下面的示例中,第一个 alert
显示 null
<html>
<head>
<script>
alert( "From HEAD: " + document.body ); // null, there's no <body> yet
</script>
</head>
<body>
<script>
alert( "From BODY: " + document.body ); // HTMLBodyElement, now it exists
</script>
</body>
</html>
null
表示“不存在”在 DOM 中,null
值表示“不存在”或“没有这样的节点”。
子节点:childNodes、firstChild、lastChild
从现在开始,我们将使用两个术语
- 子节点(或子代) – 直接子代的元素。换句话说,它们恰好嵌套在给定的元素中。例如,
<head>
和<body>
是<html>
元素的子代。 - 后代 – 嵌套在给定元素中的所有元素,包括子代、其子代,依此类推。
例如,这里 <body>
的子代有 <div>
和 <ul>
(以及一些空白文本节点)
<html>
<body>
<div>Begin</div>
<ul>
<li>
<b>Information</b>
</li>
</ul>
</body>
</html>
…而 <body>
的后代不仅包括直接子代 <div>
、<ul>
,还包括更深层次的嵌套元素,例如 <li>
(<ul>
的子代)和 <b>
(<li>
的子代)——整个子树。
childNodes
集合列出了所有子节点,包括文本节点。
下面的示例显示了 document.body
的子节点
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
for (let i = 0; i < document.body.childNodes.length; i++) {
alert( document.body.childNodes[i] ); // Text, DIV, Text, UL, ..., SCRIPT
}
</script>
...more stuff...
</body>
</html>
请注意这里一个有趣的细节。如果我们运行上面的示例,显示的最后一个元素是 <script>
。事实上,文档下面还有更多内容,但在脚本执行时,浏览器尚未读取它,因此脚本看不到它。
属性 firstChild
和 lastChild
可以快速访问第一个和最后一个子节点。
它们只是简写。如果存在子节点,则以下内容始终为真
elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild
还有一个特殊函数 elem.hasChildNodes()
,用于检查是否有任何子节点。
DOM 集合
正如我们所看到的,childNodes
看起来像一个数组。但实际上它不是一个数组,而是一个集合——一个特殊的类似数组的可迭代对象。
有两个重要的后果
- 我们可以使用
for..of
来遍历它
for (let node of document.body.childNodes) {
alert(node); // shows all nodes from the collection
}
这是因为它可迭代(提供了 Symbol.iterator
属性,这是必需的)。
- 数组方法不起作用,因为它不是一个数组
alert(document.body.childNodes.filter); // undefined (there's no filter method!)
第一件事很好。第二件事是可以容忍的,因为如果我们想要数组方法,我们可以使用 Array.from
从集合中创建一个“真实”数组
alert( Array.from(document.body.childNodes).filter ); // function
DOM 集合,甚至更多——本章中列出的所有导航属性都是只读的。
我们不能通过分配 childNodes[i] = ...
来用其他东西替换子元素。
更改 DOM 需要其他方法。我们将在下一章看到它们。
几乎所有 DOM 集合(除了少数例外)都是动态的。换句话说,它们反映了 DOM 的当前状态。
如果我们保留对 elem.childNodes
的引用,并在 DOM 中添加/删除节点,那么它们会自动出现在集合中。
for..in
循环集合集合使用 for..of
可迭代。有时人们尝试为此使用 for..in
。
请不要这样做。for..in
循环遍历所有可枚举属性。集合有一些我们通常不想获取的“额外”罕用属性
<body>
<script>
// shows 0, 1, length, item, values and more.
for (let prop in document.body.childNodes) alert(prop);
</script>
</body>
兄弟节点和父节点
兄弟节点 是同一父节点的子节点。
例如,这里 <head>
和 <body>
是兄弟节点
<html>
<head>...</head><body>...</body>
</html>
<body>
被称为<head>
的“下一个”或“右”兄弟节点,<head>
被称为<body>
的“上一个”或“左”兄弟节点。
下一个兄弟节点在 nextSibling
属性中,上一个兄弟节点在 previousSibling
中。
父节点可用作 parentNode
。
例如
// parent of <body> is <html>
alert( document.body.parentNode === document.documentElement ); // true
// after <head> goes <body>
alert( document.head.nextSibling ); // HTMLBodyElement
// before <body> goes <head>
alert( document.body.previousSibling ); // HTMLHeadElement
仅限元素的导航
上面列出的导航属性引用所有节点。例如,在 childNodes
中,我们可以看到文本节点、元素节点,甚至注释节点(如果存在)。
但是对于许多任务,我们不需要文本或注释节点。我们想要操作表示标签并形成页面结构的元素节点。
所以让我们看看更多仅考虑元素节点的导航链接
这些链接与上面给出的类似,只是里面有Element
单词
children
– 仅那些是元素节点的子级。firstElementChild
、lastElementChild
– 第一个和最后一个元素子级。previousElementSibling
、nextElementSibling
– 邻近元素。parentElement
– 父元素。
parentElement
?父级可以不是元素吗?parentElement
属性返回“元素”父级,而parentNode
返回“任何节点”父级。这些属性通常是相同的:它们都获取父级。
除了document.documentElement
alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null
原因是根节点document.documentElement
(<html>
) 的父级是document
。但是document
不是元素节点,所以parentNode
返回它,而parentElement
不返回。
当我们想从任意元素elem
向上移动到<html>
,但不想移动到document
时,此详细信息可能有用
while(elem = elem.parentElement) { // go up till <html>
alert( elem );
}
让我们修改上面示例中的一个:用children
替换childNodes
。现在它只显示元素
<html>
<body>
<div>Begin</div>
<ul>
<li>Information</li>
</ul>
<div>End</div>
<script>
for (let elem of document.body.children) {
alert(elem); // DIV, UL, DIV, SCRIPT
}
</script>
...
</body>
</html>
更多链接:表格
到目前为止,我们已经描述了基本的导航属性。
某些类型的 DOM 元素可能会提供特定于其类型的附加属性,以方便使用。
表格就是一个很好的例子,并且代表了一个特别重要的案例
<table>
元素支持(除了上面给出的)这些属性
table.rows
– 表格中<tr>
元素的集合。table.caption/tHead/tFoot
– 对元素<caption>
、<thead>
、<tfoot>
的引用。table.tBodies
–<tbody>
元素的集合(根据标准可以有很多,但至少总有一个 - 即使它不在源 HTML 中,浏览器也会将其放入 DOM 中)。
<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>
中的单元格编号。
使用示例
<table id="table">
<tr>
<td>one</td><td>two</td>
</tr>
<tr>
<td>three</td><td>four</td>
</tr>
</table>
<script>
// get td with "two" (first row, second column)
let td = table.rows[0].cells[1];
td.style.backgroundColor = "red"; // highlight it
</script>
规范:表格数据。
对于 HTML 表单,还有其他导航属性。当我们开始使用表单时,稍后会介绍它们。
总结
给定一个 DOM 节点,我们可以使用导航属性转到它的直接相邻节点。
它们有两个主要集合
- 对于所有节点:
parentNode
、childNodes
、firstChild
、lastChild
、previousSibling
、nextSibling
。 - 仅对于元素节点:
parentElement
、children
、firstElementChild
、lastElementChild
、previousElementSibling
、nextElementSibling
。
某些类型的 DOM 元素(例如表格)提供其他属性和集合来访问其内容。
评论
<code>
标签,对于多行 - 将它们包装在<pre>
标签中,对于超过 10 行 - 使用沙盒(plnkr、jsbin、codepen…)