2021 年 12 月 12 日

浏览 DOM

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>
在 DOM 世界中,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>。事实上,文档下面还有更多内容,但在脚本执行时,浏览器尚未读取它,因此脚本看不到它。

属性 firstChildlastChild 可以快速访问第一个和最后一个子节点。

它们只是简写。如果存在子节点,则以下内容始终为真

elem.childNodes[0] === elem.firstChild
elem.childNodes[elem.childNodes.length - 1] === elem.lastChild

还有一个特殊函数 elem.hasChildNodes(),用于检查是否有任何子节点。

DOM 集合

正如我们所看到的,childNodes 看起来像一个数组。但实际上它不是一个数组,而是一个集合——一个特殊的类似数组的可迭代对象。

有两个重要的后果

  1. 我们可以使用 for..of 来遍历它
for (let node of document.body.childNodes) {
  alert(node); // shows all nodes from the collection
}

这是因为它可迭代(提供了 Symbol.iterator 属性,这是必需的)。

  1. 数组方法不起作用,因为它不是一个数组
alert(document.body.childNodes.filter); // undefined (there's no filter method!)

第一件事很好。第二件事是可以容忍的,因为如果我们想要数组方法,我们可以使用 Array.from 从集合中创建一个“真实”数组

alert( Array.from(document.body.childNodes).filter ); // function
DOM 集合是只读的

DOM 集合,甚至更多——本章中列出的所有导航属性都是只读的。

我们不能通过分配 childNodes[i] = ... 来用其他东西替换子元素。

更改 DOM 需要其他方法。我们将在下一章看到它们。

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 – 仅那些是元素节点的子级。
  • firstElementChildlastElementChild – 第一个和最后一个元素子级。
  • previousElementSiblingnextElementSibling – 邻近元素。
  • 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 节点,我们可以使用导航属性转到它的直接相邻节点。

它们有两个主要集合

  • 对于所有节点:parentNodechildNodesfirstChildlastChildpreviousSiblingnextSibling
  • 仅对于元素节点:parentElementchildrenfirstElementChildlastElementChildpreviousElementSiblingnextElementSibling

某些类型的 DOM 元素(例如表格)提供其他属性和集合来访问其内容。

任务

重要性:5

查看此页面

<html>
<body>
  <div>Users:</div>
  <ul>
    <li>John</li>
    <li>Pete</li>
  </ul>
</body>
</html>

对于以下每个内容,提供至少一种访问它们的方法

  • <div> DOM 节点?
  • <ul> DOM 节点?
  • 第二个 <li>(带有 Pete)?

有很多方法,例如

<div> DOM 节点

document.body.firstElementChild
// or
document.body.children[0]
// or (the first node is space, so we take 2nd)
document.body.childNodes[1]

<ul> DOM 节点

document.body.lastElementChild
// or
document.body.children[1]

第二个 <li>(带有 Pete)

// get <ul>, and then get its last element child
document.body.lastElementChild.lastElementChild
重要性:5

如果 elem – 是一个任意 DOM 元素节点…

  • elem.lastChild.nextSibling 是否始终为 null
  • elem.children[0].previousSibling 是否始终为 null
  1. 是的,正确。元素 elem.lastChild 始终是最后一个,它没有 nextSibling
  2. 不,错误,因为 elem.children[0] 是元素中的第一个子级。但在它之前可能存在非元素节点。因此,previousSibling 可能是一个文本节点。

请注意:对于这两种情况,如果没有子级,则会发生错误。

如果没有子元素,elem.lastChildnull,因此我们无法访问 elem.lastChild.nextSibling。集合 elem.children 为空(如空数组 [])。

重要性:5

编写代码以将所有对角线表格单元格涂成红色。

你需要从 <table> 获取所有对角线 <td>,并使用以下代码将它们涂色

// td should be the reference to the table cell
td.style.backgroundColor = 'red';

结果应该是

为任务打开沙盒。

我们将使用 rowscells 属性来访问对角线表格单元格。

在沙盒中打开解决方案。

教程地图

评论

在评论之前阅读此内容...
  • 如果你有改进建议 - 请 提交 GitHub 问题或提交拉取请求,而不是评论。
  • 如果你无法理解文章中的某些内容 - 请详细说明。
  • 要插入几行代码,请使用 <code> 标签,对于多行 - 将它们包装在 <pre> 标签中,对于超过 10 行 - 使用沙盒(plnkrjsbincodepen…)