DOM は要素やそれらのコンテンツに対して何でもすることができますが、最初に対応する DOM オブジェクトに到達して、変数に入れる必要があります。それから要素やコンテンツを変更することができます。

DOM 上のすべての操作は document オブジェクトから始まります。そこから任意のノードにアクセスできます。

これは DOM ノード間を移動できるリンクの図です。:

それらについてより深く議論しましょう。

トップ: documentElement と body

一番上のツリーノードは documet のプロパティとして直接利用可能です:

<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 は利用できません。なぜならブラウザはまだ body を呼んでいないからです。

従って、下の例では最初の alertnull を表示します:

<html>

<head>
  <script>
    alert( "From HEAD: " + document.body ); // null, まだ <body> はありません
  </script>
</head>

<body>

  <script>
    alert( "From BODY: " + document.body ); // HTMLBodyElement, 今は存在します
  </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> の子)のような、よりネストされた要素を取得 – サブツリー全体です。

子ノード のコレクションは、テキストノードを含むすべての子ノードへのアクセスを提供します。

下の例は、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>
  ...他の要素...
</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); // コレクションのすべてのノードを表示する
}

これは反復可能(必須で Symbol.iterator プロパティを提供する)のためです。

  1. 配列メソッドは動作しません、なぜなら配列ではないからです:
alert(document.body.childNodes.filter); // undefined (フィルタメソッドを持っていません!)

最初の1つ目は良いです。2つ目は許容できます。なぜなら、配列メソッドが必要な場合、コレクションから “本当の” 配列を作るために Array.from を使うことができるからです。:

alert( Array.from(document.body.childNodes).filter ); // これで使えます
DOM コレクションは読み取り専用です

DOM コレクションやさらに – このチャプターにリストされている すべての ナビゲーションプロパティは読み取り専用です。

代入 childNodes[i] = ... などにより子ノードを置き換えることはできません。

DOM の変更は他のメソッドを必要とします。それらについては次のチャプターで見ていきましょう。

DOM コレクションはライブです

マイナーな例外を伴うほとんどすべての DOM コレクションは ライブ です 。つまり、それらは DOM の現在の状態を反映しています。

もし elem.childNodes への参照を維持し、DOM にノードを追加/削除すると、コレクションの中に自動的に反映されます。

コレクションをループするために、for..in を使わないでください

Collections are iterable using for..of. Sometimes people try to use for..in for that.

使わないでください。for..in ループはすべての列挙可能なプロパティを反復します。そしてコレクションは, 通常取得したいと思わないいくつかの “余分な” ほとんど使われないプロパティを持っています。:

<body>
<script>
  // 0, 1, length, item, values などが表示されます。
  for(let prop in document.body.childNodes) alert(prop);
</script>
</body>

兄弟と親

兄弟(Siblings) は同じ親(parent)の子ノードです。例えば、<head><body> は兄弟です:

  • <body><head> の “次の” または “右の” 兄弟と言われます。
  • <head> <body> の “前の” または “左の” 兄弟と言われます。

親は parentNode として利用可能です。

同じ親において、次のノード(次の兄弟) は nextSibling であり、前のノードは previousSibling です。

例えば:

<html><head></head><body><script>
  // HTML は余分な "ブランクの" テキストノードを避けるために密集しています。

  // <body> の親は <html> です。
  alert( document.body.parentNode === document.documentElement ); // true

  // <head> の後は <body> に行きます。
  alert( document.head.nextSibling ); // HTMLBodyElement

  // <body> の前は <head> です。
  alert( document.body.previousSibling ); // HTMLHeadElement
</script></body></html>

Element-only navigation

上でリストされているナビゲーションプロパティは すべての ノードを参照しています。例えば、childNodes では、テキストノード、要素ノードの両方を、存在する場合にはコメントノードも見ることができます。

しかし、多くのタスクでは、テキストノードやコメントノードは必要ありません。 タグを表し、ページの構造を形成する要素ノードを操作したいです。

なので、要素ノード だけを考慮にいれたナビゲーションリンクをもっと見てみましょう:

リンクは上で与えられたものと似ており、Element という言葉が内部にあります:

  • children – 要素ノードの子のみです。
  • firstElementChild, lastElementChild – 最初/最後の要素の子です。
  • previousElementSibling, nextElementSibling – 隣の要素です。
  • parentElement – 親の要素です。
なぜ parentElement? 親は要素 ではない 場合はありますか?

parentElement プロパティは “要素” の親を返しますが、parentNode は “任意のノード” の親を返します。それらのプロパティは通常同じです: 両方とも親を取得します。

document.documentElement の例外を除いて:

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

言い換えると、documentElement (<html>) はルートノードです。公式にはその親として document を持っています。しかし、document は要素ノードではないので、parentNode はそれを返し、parentElement はそうではありません。

上の例の1つを修正してみましょう: 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>

他のリンク: tables

今まで、基本的なナビゲーションプロパティを説明しました。

特定の種類の DOM 要素は、便宜上、その種類に固有の追加のプロパティを提供することがあります。

テーブルはその素晴らしい例であり重要なケースです。

<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 – 囲んでいる <thead>/<tbody> の内部にある与えられた <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>
  // 最初の行の2つ目のセルのコンテンツを取得
  alert( table.rows[0].cells[1].innerHTML ) // "two"
</script>

仕様: tabular data.

HTMLフォームのための追加のナビゲーションプロパティもあります。 フォームを使って作業を開始するときにそれらを見ていきます。

サマリ

与えられた DOM ノードで、ナビゲーションプロパティを使用することで直接隣接ノードに移動できます。

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

  • すべてのノードを取得: parentNode, childNodes, firstChild, lastChild, previousSibling, nextSibling.
  • 要素ノードのみの取得: parentElement, children, firstElementChild, lastElementChild, previousElementSibling, nextElementSibling

DOM 要素のいくつかの種類 – e.g. tables – はそれらのコンテンツにアクセスするための追加のプロパティやコレクションを提供します。

タスク

重要性: 5

次のページに対して:

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

次へのアクセスの方法はどうなるでしょう?:

  • <div> DOM ノード?
  • <ul> DOM ノード?
  • 2つ目の <li> (Pete を持つ)?

多くの方法があります、例えば:

<div> DOM ノード:

document.body.firstElementChild
// or
document.body.children[0]
// or (最初のノードはスペースなので、2つ目を取得します)
document.body.childNodes[1]

<ul> DOM ノード:

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

2つ目の <li> (Pete):

// <ul> を取得し、その最後の要素の子を取得
document.body.lastElementChild.lastElementChild
重要性: 5

もし elem – が任意の DOM 要素ノードの場合…

  • elem.lastChild.nextSibling は常に null である、は真実?
  • elem.children[0].previousSibling は常に null である、は真実?
  1. はい、真実です。要素 elem.lastChild は常に最後の要素で、nextSibling を持っていません。なので、子要素がある場合は yes です。
  2. いいえ、間違いです。なぜなら、elem.children[0] は要素の中の最初子だからです。しかし、その前に非要素ノードがある可能性があります。なので、previousSibling はテキストノードかもしれません。

両方のケースに対し、子がいない場合にはエラーになることに注意してください。例えば、elem.lastChildnull の場合、 elem.lastChild.nextSibling にアクセスすることはできません。

重要性: 5

対角線上のすべての表のセルを赤でペイントするコードを書いてください。

<table> からすべての体格 <td> を取得し、コードを使ってペイントする必要があります。:

// td はテーブルセルへの参照
td.style.backgroundColor = 'red';

結果はこのようになるはずです:

タスクのためのサンドボックスを開く

対角テーブルセルにアクセスするため、rowscells プロパティを使っています。

サンドボックスで解答を開く

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。