Javascript 遍歷 DOM

2023-02-17 10:54 更新

DOM 讓我們可以對(duì)元素和它們中的內(nèi)容做任何事,但是首先我們需要獲取到對(duì)應(yīng)的 DOM 對(duì)象。

對(duì) DOM 的所有操作都是以 ?document? 對(duì)象開(kāi)始。它是 DOM 的主“入口點(diǎn)”。從它我們可以訪問(wèn)任何節(jié)點(diǎn)。

這里是一張描述對(duì)象間鏈接的圖片,通過(guò)這些鏈接我們可以在 DOM 節(jié)點(diǎn)之間移動(dòng)。


讓我們更詳細(xì)地討論它們吧。

在最頂層:documentElement 和 body

最頂層的樹(shù)節(jié)點(diǎn)可以直接作為 document 的屬性來(lái)使用:

?<html>? = ?document.documentElement ?

最頂層的 document 節(jié)點(diǎn)是 ?document.documentElement?。這是對(duì)應(yīng) ?<html>? 標(biāo)簽的 DOM 節(jié)點(diǎn)。

?<body>? = ?document.body ?

另一個(gè)被廣泛使用的 DOM 節(jié)點(diǎn)是 ?<body>? 元素 —— ?document.body?。

?<head>? = ?document.head ?

?<head>? 標(biāo)簽可以通過(guò) ?document.head? 訪問(wèn)。

這里有個(gè)問(wèn)題:document.body 的值可能是 null

腳本無(wú)法訪問(wèn)在運(yùn)行時(shí)不存在的元素。

尤其是,如果一個(gè)腳本是在 <head> 中,那么腳本是訪問(wèn)不到 document.body 元素的,因?yàn)闉g覽器還沒(méi)有讀到它。

所以,下面例子中的第一個(gè) alert 顯示 null

<html>

<head>
  <script>
    alert( "From HEAD: " + document.body ); // null,這里目前還沒(méi)有 <body>
  </script>
</head>

<body>

  <script>
    alert( "From BODY: " + document.body ); // HTMLBodyElement,現(xiàn)在存在了
  </script>

</body>
</html>

在 DOM 的世界中,?null? 就意味著“不存在”

在 DOM 中,null 值就意味著“不存在”或者“沒(méi)有這個(gè)節(jié)點(diǎn)”。

子節(jié)點(diǎn):childNodes,firstChild,lastChild

從現(xiàn)在開(kāi)始,我們將使用下面這兩個(gè)術(shù)語(yǔ):

  • 子節(jié)點(diǎn)(或者叫作子) —— 對(duì)應(yīng)的是直系的子元素。換句話說(shuō),它們被完全嵌套在給定的元素中。例如,?<head>? 和 ?<body>? 就是 ?<html>? 元素的子元素。
  • 子孫元素 —— 嵌套在給定元素中的所有元素,包括子元素,以及子元素的子元素等。

例如,這里 <body> 有子元素 <div> 和 <ul>(以及一些空白的文本節(jié)點(diǎn)):

<html>
<body>
  <div>Begin</div>

  <ul>
    <li>
      <b>Information</b>
    </li>
  </ul>
</body>
</html>

……<body> 元素的子孫元素不僅包含直接的子元素 <div> 和 <ul>,還包含像 <li><ul> 的子元素)和 <b><li> 的子元素)這樣的元素 — 整個(gè)子樹(shù)。

childNodes 集合列出了所有子節(jié)點(diǎn),包括文本節(jié)點(diǎn)。

下面這個(gè)例子顯示了 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>

請(qǐng)注意這里的一個(gè)有趣的細(xì)節(jié)。如果我們運(yùn)行上面這個(gè)例子,所顯示的最后一個(gè)元素是 <script>。實(shí)際上,文檔下面還有很多東西,但是在這個(gè)腳本運(yùn)行的時(shí)候,瀏覽器還沒(méi)有讀到下面的內(nèi)容,所以這個(gè)腳本也就看不到它們。

firstChild 和 lastChild 屬性是訪問(wèn)第一個(gè)和最后一個(gè)子元素的快捷方式。

它們只是簡(jiǎn)寫(xiě)。如果元素存在子節(jié)點(diǎn),那么下面的腳本運(yùn)行結(jié)果將是 true:

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

這里還有一個(gè)特別的函數(shù) elem.hasChildNodes() 用于檢查節(jié)點(diǎn)是否有子節(jié)點(diǎn)。

DOM 集合

正如我們看到的那樣,childNodes 看起來(lái)就像一個(gè)數(shù)組。但實(shí)際上它并不是一個(gè)數(shù)組,而是一個(gè) 集合 —— 一個(gè)類數(shù)組的可迭代對(duì)象。

這個(gè)性質(zhì)會(huì)導(dǎo)致兩個(gè)重要的結(jié)果:

  1. 我們可以使用 ?for..of? 來(lái)迭代它:
  2. for (let node of document.body.childNodes) {
      alert(node); // 顯示集合中的所有節(jié)點(diǎn)
    }

    這是因?yàn)榧鲜强傻模ㄌ峁┝怂枰?nbsp;Symbol.iterator 屬性)。

  3. 無(wú)法使用數(shù)組的方法,因?yàn)樗皇且粋€(gè)數(shù)組:
  4. alert(document.body.childNodes.filter); // undefined(這里沒(méi)有 filter 方法?。?/code>

    集合的性質(zhì)所得到的第一個(gè)結(jié)果很不錯(cuò)。第二個(gè)結(jié)果也還可以忍受,因?yàn)槿绻覀兿胍褂脭?shù)組的方法的話,我們可以使用 Array.from 方法來(lái)從集合創(chuàng)建一個(gè)“真”數(shù)組:

    alert( Array.from(document.body.childNodes).filter ); // function

DOM 集合是只讀的

DOM 集合,甚至可以說(shuō)本章中列出的 所有 導(dǎo)航(navigation)屬性都是只讀的。

我們不能通過(guò)類似 childNodes[i] = ... 的操作來(lái)替換一個(gè)子節(jié)點(diǎn)。

修改子節(jié)點(diǎn)需要使用其它方法。我們將會(huì)在下一章中看到它們。

DOM 集合是實(shí)時(shí)的

除小部分例外,幾乎所有的 DOM 集合都是 實(shí)時(shí) 的。換句話說(shuō),它們反映了 DOM 的當(dāng)前狀態(tài)。

如果我們保留一個(gè)對(duì) elem.childNodes 的引用,然后向 DOM 中添加/移除節(jié)點(diǎn),那么這些節(jié)點(diǎn)的更新會(huì)自動(dòng)出現(xiàn)在集合中。

不要使用 ?for..in? 來(lái)遍歷集合

可以使用 for..of 對(duì)集合進(jìn)行迭代。但有時(shí)候人們會(huì)嘗試使用 for..in 來(lái)迭代集合。

請(qǐng)不要這么做。for..in 循環(huán)遍歷的是所有可枚舉的(enumerable)屬性。集合還有一些“額外的”很少被用到的屬性,通常這些屬性也是我們不期望得到的:

<body>
<script>
  // 顯示 0,1,length,item,values 及其他。
  for (let prop in document.body.childNodes) alert(prop);
</script>
</body>

兄弟節(jié)點(diǎn)和父節(jié)點(diǎn)

兄弟節(jié)點(diǎn)(sibling) 是指有同一個(gè)父節(jié)點(diǎn)的節(jié)點(diǎn)。

例如,<head> 和 <body> 就是兄弟節(jié)點(diǎn):

<html>
  <head>...</head><body>...</body>
</html>
  • ?<body>? 可以說(shuō)是 ?<head>? 的“下一個(gè)”或者“右邊”兄弟節(jié)點(diǎn)。
  • ?<head>? 可以說(shuō)是 ?<body>? 的“前一個(gè)”或者“左邊”兄弟節(jié)點(diǎn)。

下一個(gè)兄弟節(jié)點(diǎn)在 nextSibling 屬性中,上一個(gè)是在 previousSibling 屬性中。

可以通過(guò) parentNode 來(lái)訪問(wèn)父節(jié)點(diǎn)。

例如:

// <body> 的父節(jié)點(diǎn)是 <html>
alert( document.body.parentNode === document.documentElement ); // true

// <head> 的后一個(gè)是 <body>
alert( document.head.nextSibling ); // HTMLBodyElement

// <body> 的前一個(gè)是 <head>
alert( document.body.previousSibling ); // HTMLHeadElement

純?cè)貙?dǎo)航

上面列出的導(dǎo)航(navigation)屬性引用 所有 節(jié)點(diǎn)。例如,在 childNodes 中我們可以看到文本節(jié)點(diǎn),元素節(jié)點(diǎn),甚至包括注釋節(jié)點(diǎn)(如果它們存在的話)。

但是對(duì)于很多任務(wù)來(lái)說(shuō),我們并不想要文本節(jié)點(diǎn)或注釋節(jié)點(diǎn)。我們希望操縱的是代表標(biāo)簽的和形成頁(yè)面結(jié)構(gòu)的元素節(jié)點(diǎn)。

所以,讓我們看看更多只考慮 元素節(jié)點(diǎn) 的導(dǎo)航鏈接(navigation link):


這些鏈接和我們?cè)谏厦嫣岬竭^(guò)的類似,只是在詞中間加了 Element

  • ?children? —— 僅那些作為元素節(jié)點(diǎn)的子代的節(jié)點(diǎn)。
  • ?firstElementChild?,?lastElementChild? —— 第一個(gè)和最后一個(gè)子元素。
  • ?previousElementSibling?,?nextElementSibling? —— 兄弟元素。
  • ?parentElement? —— 父元素。

為什么是 ?parentElement?? 父節(jié)點(diǎn)可以不是一個(gè)元素嗎?

parentElement 屬性返回的是“元素類型”的父節(jié)點(diǎn),而 parentNode 返回的是“任何類型”的父節(jié)點(diǎn)。這些屬性通常來(lái)說(shuō)是一樣的:它們都是用于獲取父節(jié)點(diǎn)。

唯一的例外就是 document.documentElement

alert( document.documentElement.parentNode ); // document
alert( document.documentElement.parentElement ); // null

因?yàn)楦?jié)點(diǎn) document.documentElement<html>)的父節(jié)點(diǎn)是 document。但 document 不是一個(gè)元素節(jié)點(diǎn),所以 parentNode 返回了 document,但 parentElement 返回的是 null。

當(dāng)我們想從任意節(jié)點(diǎn) elem 到 <html> 而不是到 document 時(shí),這個(gè)細(xì)節(jié)可能很有用:

while(elem = elem.parentElement) { // 向上,直到 <html>
  alert( elem );
}

讓我們修改上面的一個(gè)示例:用 children 來(lái)替換 childNodes。現(xiàn)在它只顯示元素:

<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>

更多鏈接:表格

到現(xiàn)在,我們已經(jīng)描述了基本的導(dǎo)航(navigation)屬性。

方便起見(jiàn),某些類型的 DOM 元素可能會(huì)提供特定于其類型的其他屬性。

表格(Table)是一個(gè)很好的例子,它代表了一個(gè)特別重要的情況:

<table> 元素支持 (除了上面給出的,之外) 以下屬性:

  • ?table.rows? —— ?<tr>? 元素的集合。
  • ?table.caption/tHead/tFoot? —— 引用元素 ?<caption>?,?<thead>?,?<tfoot>?。
  • ?table.tBodies? —— ?<tbody>? 元素的集合(根據(jù)標(biāo)準(zhǔn)還有很多元素,但是這里至少會(huì)有一個(gè) —— 即使沒(méi)有被寫(xiě)在 HTML 源文件中,瀏覽器也會(huì)將其放入 DOM 中)。

<thead>,<tfoot>,<tbody> 元素提供了 rows 屬性:

  • ?tbody.rows? —— 表格內(nèi)部 ?<tr>? 元素的集合。

<tr>

  • ?tr.cells? —— 在給定 ?<tr>? 中的 ?<td>? 和 ?<th>? 單元格的集合。
  • ?tr.sectionRowIndex? —— 給定的 ?<tr>? 在封閉的 ?<thead>/<tbody>/<tfoot>? 中的位置(索引)。
  • ?tr.rowIndex? —— 在整個(gè)表格中 ?<tr>? 的編號(hào)(包括表格的所有行)。

<td> 和 <th>

  • ?td.cellIndex? —— 在封閉的 ?<tr>? 中單元格的編號(hào)。

用法示例:

<table id="table">
  <tr>
    <td>one</td><td>two</td>
  </tr>
  <tr>
    <td>three</td><td>four</td>
  </tr>
</table>

<script>
  // 獲取帶有 "two" 的 td(第一行,第二列)
  let td = table.rows[0].cells[1];
  td.style.backgroundColor = "red"; // highlight it
</script>

規(guī)范:tabular data

HTML 表單(form)還有其它導(dǎo)航(navigation)屬性。稍后當(dāng)我們開(kāi)始使用表單(form)時(shí),我們將對(duì)其進(jìn)行研究。

總結(jié)

給定一個(gè) DOM 節(jié)點(diǎn),我們可以使用導(dǎo)航(navigation)屬性訪問(wèn)其直接的鄰居。

這些屬性主要分為兩組:

  • 對(duì)于所有節(jié)點(diǎn):?parentNode?,?childNodes?,?firstChild?,?lastChild?,?previousSibling?,?nextSibling?。
  • 僅對(duì)于元素節(jié)點(diǎn):?parentElement?,?children?,?firstElementChild?,?lastElementChild?,?previousElementSibling?,?nextElementSibling?。

某些類型的 DOM 元素,例如 table,提供了用于訪問(wèn)其內(nèi)容的其他屬性和集合。

任務(wù)


DOM 子節(jié)點(diǎn)

重要程度: 5

對(duì)于這個(gè)頁(yè)面:

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

對(duì)于以下各項(xiàng),請(qǐng)給出至少一種訪問(wèn)方式:

  • ?<div>? DOM 節(jié)點(diǎn)?
  • ?<ul>? DOM 節(jié)點(diǎn)?
  • 第二個(gè) ?<li>? 節(jié)點(diǎn)(即包含 Pete 的節(jié)點(diǎn))?

解決方案

這里有很多種方法,例如:

獲取 <div> DOM 節(jié)點(diǎn):

document.body.firstElementChild
// 或
document.body.children[0]
// 或(第一個(gè)節(jié)點(diǎn)是空格,所以我們應(yīng)該獲取的是第二個(gè))
document.body.childNodes[1]

獲取 <ul> DOM 節(jié)點(diǎn):

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

獲取第二個(gè) <li>(即包含 Pete 的節(jié)點(diǎn)):

// 獲取 <ul>,然后獲取它的最后一個(gè)子元素
document.body.lastElementChild.lastElementChild

兄弟節(jié)點(diǎn)問(wèn)題

重要程度: 5

如果 ?elem? 是任意一個(gè) DOM 元素節(jié)點(diǎn)……

  • ?elem.lastChild.nextSibling? 值一直都是 ?null?,這個(gè)判定是不是真的?
  • ?elem.children[0].previousSibling? 值一直都是 ?null?,這個(gè)判定是不是真的?

解決方案

  1. 是的,這是真的。?elem.lastChild? 就是最后一個(gè)節(jié)點(diǎn),它沒(méi)有 ?nextSibling?。
  2. 不,這是錯(cuò)的,因?yàn)?nbsp;?elem.children[0]? 是元素中的第一個(gè)子元素。但是在它前面可能存在非元素的節(jié)點(diǎn)。所以 ?previousSibling? 可能是一個(gè)文本節(jié)點(diǎn)。

請(qǐng)注意,對(duì)于這兩種情況,如果沒(méi)有子節(jié)點(diǎn),那么就會(huì)報(bào)錯(cuò)。

如果這里沒(méi)有子節(jié)點(diǎn),那么 elem.lastChild 是 null,所以我們就訪問(wèn)不到 elem.lastChild.nextSibling。并且 elem.children 是空的(像一個(gè)空數(shù)組一樣 [])。


選擇所有對(duì)角單元格

重要程度: 5

編寫(xiě)代碼來(lái)把表格中的對(duì)角單元格都繪制成紅色。

你需要用代碼從 <table> 中獲取所有的對(duì)角單元格 <td>,然后繪制它們:

// td 表示的是對(duì)單元格的引用
td.style.backgroundColor = 'red';

結(jié)果應(yīng)該如下所示:



解決方案

我們將使用 rows 和 cells 屬性來(lái)獲取表格中的對(duì)角單元格。

<!DOCTYPE HTML>
<html>
<head>
  <style>
    table {
      border-collapse: collapse;
    }

    td {
      border: 1px solid black;
      padding: 3px 5px;
    }
  </style>
</head>

<body>
  <table>
    <tr>
      <td>1:1</td>
      <td>2:1</td>
      <td>3:1</td>
      <td>4:1</td>
      <td>5:1</td>
    </tr>
    <tr>
      <td>1:2</td>
      <td>2:2</td>
      <td>3:2</td>
      <td>4:2</td>
      <td>5:2</td>
    </tr>
    <tr>
      <td>1:3</td>
      <td>2:3</td>
      <td>3:3</td>
      <td>4:3</td>
      <td>5:3</td>
    </tr>
    <tr>
      <td>1:4</td>
      <td>2:4</td>
      <td>3:4</td>
      <td>4:4</td>
      <td>5:4</td>
    </tr>
    <tr>
      <td>1:5</td>
      <td>2:5</td>
      <td>3:5</td>
      <td>4:5</td>
      <td>5:5</td>
    </tr>
  </table>
  <script>
    let table = document.body.firstElementChild;

    for (let i = 0; i < table.rows.length; i++) {
      let row = table.rows[i];
      row.cells[i].style.backgroundColor = 'red';
    }
  </script>
</body>
</html>


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)