uhyohyo.net

JavaScript初級者から中級者になろう

二章第七回 木構造の操作:さまざまな機能2

このページの最終更新日:

兄弟ノード

DOMには、兄弟ノードを取得する方法もあります。

ここで、兄弟ノードとは、同じ親を持つノードのことです。例えば、

AAA
  • BBB
  • CCC
  • DDD

という木構造があったとき、BBB,CCC,DDDは全て、同じAAAという親を持つ兄弟ノードです。

あるノードの兄弟ノードを取得するには、プロパティpreviousSiblingnextSiblingを使います。

previousSiblingはそのノードの1つ前の兄弟、nextSiblingは1つ後の兄弟です。

実際のサンプルで見てみましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p><strong>a</strong><em id="aaa">b</em><ins>c</ins></p>

    <script type="text/javascript">
      var aaa = document.getElementById('aaa');

      var prev = aaa.previousSibling;
      var next = aaa.nextSibling;

      console.log(prev.tagName);
      console.log(next.tagName);
    </script>
  </body>
</html>

このサンプルの木構造はこうです。

p
  • strong
    • #text
  • em
    • #text
  • ins
    • #text

ここで、変数aaaには、"aaa"というidを持つ要素、つまりem要素のノードを代入しています。

prevとnextには、それぞれpreviosSiblingとnextSiblingが代入されています。

つまり、

p
  • strong ← previousSibling
    • #text
  • em
    • #text
  • ins ← nextSibling
    • #text

このようになっているから、prevにはstrong要素のノード、nextにはins要素のノードが代入されているはずです。

それを確かめているのが次の2行で、tagNameを表示しています。予想通り、「STRONG」と「INS」が表示されるはずです。

ちなみに、兄弟ノードを全てまとめて(NodeListの形で)取得することは、今までの知識でもできます。aaa.parentNode.childNodesですね。

このpreviosuSiblingとnextSiblingですが、少し不便な場合もあります。それは、間にテキストノードがある場合です。次のサンプルを見てください。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p>段落1です。</p>
    <p id="abc">段落2です。</p>
    <p>段落3です。</p>

    <script type="text/javascript">
      var p = document.getElementById('abc');

      var prev = p.previousSibling;
      var next = p.nextSibling;

      console.log(prev);
      console.log(next);
    </script>
  </body>
</html>

変数pに、3つあるうちの真ん中のp要素を代入しました。このとき、前後のp要素を取得したくても、previousSiblingやnextSiblingに入っているのはテキストノードです。なぜなら、上のHTMLではp要素の間に改行があり、それがテキストノードとして現れているからです。このテキストノードは厄介な場合があります。まず、空白とか改行によって発生するテキストノードは作成者が意識していない場合が多く、プログラムのバグにつながります。また、近年、HTMLを最小化(余計な空白や改行を全部取り除く)してから配信することも行われています。この場合、最小化の前後でテキストノードの有無が変わってしまいます。

このような問題に対処できる便利なプロパティが、previousElementSiblingnextElementSiblingです。これは、それぞれ「前の要素ノード」「次の要素ノード」を取得できます。つまり、間にあるテキストノード等を無視してくれます。これを用いれば、前のサンプルでも前後のp要素を取得できます。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p>段落1です。</p>
    <p id="abc">段落2です。</p>
    <p>段落3です。</p>

    <script type="text/javascript">
      var p = document.getElementById('abc');

      var prev = p.previousElementSibling;
      var next = p.nextElementSibling;

      console.log(prev);
      console.log(next);
    </script>
  </body>
</html>

replaceChild

次に紹介するreplaceChildは、ノードが持つプロパティで、そのノードが持つある子ノードを、別のノードに置き換えます。より正確には、もともとあった子ノードを除去し、その位置に新しいノードを挿入します。

実際のサンプルを見てみましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="aaaaa">t<strong>es</strong>t</p>

    <script type="text/javascript">
      var p = document.getElementById('aaaaa');

      var newnode = document.createElement('em');
      newnode.appendChild(document.createTextNode('置き換えるノード'));

      p.replaceChild(newnode, p.firstChild);
    </script>
  </body>
</html>
        

このサンプルでは、P要素の最初の子ノードと、新しくつくったem要素を置き換えています。

まず、getElementByIdで

p      ← これ
  • #text
  • strong
    • #text
  • #text

を取得します。

その後、新しいem要素をcreateElementで作り、newnodeに代入しています。その後、そのem要素にdocument.createTextNodeで作ったテキストノードを子として追加しています。

つまり、

em
  • #text "置き換えるノード"

を作って変数newnodeに代入したということですね。

その後、いよいよp.replaceChildを呼び出しています。1つめの引数が新しく作ったノードで、2つめの引数は、p.firstChild、つまり

p
  • #text    ←これ
  • strong
    • #text
  • #text

ですね。

replaceChildは、1つめの引数が置き換え後のノードで、2つめの引数が置き換え前のノードです。

つまり、この呼び出しでは、p.firstChildをnewnodeに置き換えるという処理をしているわけです。つまり、次のようになります。

p
  • #text "t"
  • strong
    • #text "es"
  • #text "t"

↓↓↓

p
  • em
    • #text "置き換えるノード"
  • strong
    • #text "es"
  • #text "t"

画面の実行結果をみても、この通りになっていることが分かります。

cloneNode

ノードは、cloneNodeというメソッドを持っています。これは、ノードをコピーするためのものです。

というのも、ノードもオブジェクトの一種だから、var b = a;のようにしてしも、bとaは同じノードを指してしまい、うまくいきません。

また、あるノードは木構造中の1箇所にしか入れることができます。同じノードを複数箇所に配置するということはできないのです。そのようなことをしたい場合、既にあるノードと全く同じ形のノードを新しく作る必要があります。これを行ってくれるのがcloneNodeです。

サンプルを見てみましょう。

<!doctype html>
<html>
  <head>
    <title>test</title>
  </head>
  <body>
    <p id="aaaaa">t<strong>es</strong>t</p>

    <script type="text/javascript">
      var p = document.getElementById('aaaaa');

      var newnode = p.cloneNode(true);

      document.body.appendChild(newnode);
    </script>
  </body>
</html>

やっていることは簡単で、変数pにp要素を代入し、pのcloneNodeで同じノードをもう1つつくってnewnodeに代入し、それをbody要素にappendChildで追加しています。

その結果、同じ(だがノードとしては別々の)p要素が新しくページの最後に追加されました。

では、cloneNodeの引数であるtrueは何なのでしょう。実は、cloneNodeには引数としてtrueまたはfalse(すなはち、真偽値)を与えることができます。この引数にtrueを指定すると、子ノードも全てコピーします。対して、falseを指定すると、そのノードしかコピーしません。つまり、子ノードは全くない状態で新しいノードが作られます。HTMLタグでいうと、<p></p>の部分だけコピーするようなものです。用途に応じて使い分けましょう。