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.bodynullになる可能性があります

スクリプトは、実行時に存在しない要素にアクセスすることはできません。

特に、スクリプトが<head>内にある場合、ブラウザはまだ読み込んでいないため、document.bodyは使用できません。

したがって、以下の例では、最初のalertnullを表示します

<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

今後使用する用語が2つあります

  • 子ノード(または子供) - 直接の子要素です。言い換えると、これらは指定された要素に正確にネストされています。たとえば、<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は配列のように見えます。しかし実際には配列ではなく、コレクション、つまり配列のような反復可能な特殊なオブジェクトです。

これには2つの重要な結果があります。

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

配列メソッドが必要な場合は、最初に重要なのは素晴らしいことです。2番目は許容できます。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 );
}

上記の例を修正しましょう。childNodeschildrenに置き換えます。これで要素のみが表示されます

<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 – 囲まれた<thead>/<tbody>/<tfoot>内の指定された<tr>の位置(インデックス)です。
  • 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ノードが与えられると、ナビゲーションプロパティを使用してそのすぐ近くのノードに移動できます。

それらには2つの主要なセットがあります

  • すべてのノードに対して、parentNodechildNodesfirstChildlastChildpreviousSiblingnextSibling
  • 要素ノードに対してのみ、parentElementchildrenfirstElementChildlastElementChildpreviousElementSiblingnextElementSibling

テーブルなどの特定の種類のDOM要素では、追加のプロパティとコレクションが提供され、そのコンテンツにアクセスできます。

タスク

重要度: 5

このページをご覧ください。

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

次に挙げるものそれぞれについて、アクセス方法を少なくとも 1 つ示してください。

  • <div> DOM ノード?
  • <ul> DOM ノード?
  • 2 番目の <li> (ピート)?

次のような方法が数多くあります。たとえば:

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

2 番目の <li> (ピート)

// 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…) を使用します。