uhyohyo.net

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

二章第四回 木構造の操作:ノードの除去

今回は、木構造からノードをまるまる除去してしまう方法を解説します。

removeChild

それには、removeChildというメソッドを使います。これは、ノードが持つメソッドです。

まず使用例を見てみましょう。サンプルには、前回と同様のものを使います。

<!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 children = p.childNodes;
      var textnode = children.item(0);	//テキストノード
      p.removeChild(textnode);
    </script>
  </body>
</html>
        

実行すると、画面には「est」と表示されています。最初の「t」が消えてしまいました。

これは、実はこういうことです。

 p
|
├――#text      … "t"
|
├――strong
|   |
|   └――#text  … "es"
|
└――#text      … "t"

            ↓↓↓

 p
|
├――strong
|   |
|   └――#text  … "es"
|
└――#text      … "t"
        

最初のテキストノードが消えてしまいました。

removeChildは、「子ノードのうち、引数のノードが表すノードを除去する」というメソッドです。

上のサンプルで、引数はtextnodeで、textnodeはpの子ノードの0番目のノードでした。つまり、引数のノードが表すノードとは、pの0番目の子ノードということになりますね。従って、それが除去されたというわけです。

ちなみに、除去されたノードは、まったく消えて無くなったわけではありません。木構造の中にこそありませんが、どこかに存在しています。これはどういうことかというと、除去したノードは、また別の場所で使ったりできるということです。これは、また別の機会に解説します。

さて、innerHTMLでやったような書き換えを実現するには、strong要素がまだ余計です。さっき0番目の要素を削除したから、

var p = document.getElementById('aaaaa');
var children = p.childNodes;
var textnode = children.item(0);	//テキストノード
p.removeChild(textnode);
p.removeChild(children.item(1));
          

のようにして次は1番目の要素を消せばいいと考えられます。今回は、いちいち別の変数に代入せずに直接引数にしています。

しかし、実際には「est」のうち「t」が消えて「es」が残っています。これは、今回追加したremoveChildが実行されるとき、木構造は


 p
|
├――strong
|   |
|   └――#text  … "es"
|
└――#text      … "t"
          

という状態でした。これの一番目だから、実は


 p
|
├――strong        ←0番目
|   |
|   └――#text
|
└――#text         ←1番目
        

のようになって「t」を表すテキストノードが1番目になっていたのです。

最初1番目だったstrong要素のノードは、0番目に変わってしまっています。これは、もともと0番目のノードが除去されて子ノードのリストから無くなったことで詰められたからです。実際に木構造を見ると分かりやすいと思います。

つまり、strong要素を除去するには、また0番目のノードをremoveChildしないといけなかったのです。

var p = document.getElementById('aaaaa');
var children = p.childNodes;
var textnode = children.item(0);	//テキストノード
p.removeChild(textnode);

p.removeChild(children.item(0));
        

こうすることで、


 p
|
├――strong          ←これ
|   
|   └――#text  … "es"
|
└――#text      … "t"
          

を除去できるというわけです。ちなみに、さらに子を持つ要素を除去すると(今回の場合strongがさらに#textを子に持っていますね)、その子ごとまとめてなくなります。つまり、


 p
|
└――#text      … "t"
          

このようになります。

だから、上のサンプルを実行すると「t」だけ残りました。このとき、この#textはまた0番目です。そこで、

var p = document.getElementById('aaaaa');
var children = p.childNodes;
var textnode = children.item(0);	//テキストノード
p.removeChild(textnode);

p.removeChild(children.item(1));

children.item(0).nodeValue = "書き換えました";
          

のようにしてこれを書き換えれば、


 p
|
└――#text      … "書き換えました"
          

となって、見事p要素の中身は「書き換えました」だけになるわけです。

子ノードの全除去

今回の場合、p要素の中身を#textひとつだけ残しましたが、要素の子ノードを全部なくしたいということもよくあります。

そういう場合も今回のように1つずつ除去していくしかないのですが、それに適した方法があります。

まず、子ノードが何個あるか分からないから繰り返しの文を使うということは分かります。今回はwhile文を使います。

<!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 children = p.childNodes;

      while( p.hasChildNodes() ){
        p.removeChild( children.item(0) );
      }
    </script>
  </body>
</html>
        

画面には何も表示されません。

まず、一つ新しいメソッドが出ています。hasChildNodesです。これはノードが持つメソッドです。引数はありません。

このメソッドは簡単で、そのノードが子ノードを持っていればtrueを返し、持っていなければfalseを返します

つまり、このwhileは、p要素が子ノードを持っている限り繰り返し続けるというわけです。

さて、繰り返される処理は1文だけです。childrenはp要素の子ノードのNodeListなので、removeChildでp要素の0番目(最初)の要素を除去しているというわけです。

NodeListからノードを除去すると詰められるので、pが子ノードを持っている限り「0番目の子ノード」は存在します。木構造で実際の動きを見てみると、

 p
|
├――#text      ←0番目
|
├――strong
|   |
|   └――#text  ←1番目
|
└――#text      ←2番目

↓↓↓

 p
|
├――strong      ←0番目
|   |
|   └――#text
|
└――#text      ←1番目

↓↓↓

 p
|
└――#text      ←0番目

↓↓↓

 p
        

というように順番に除去されて子ノードが無くなります。最後の子ノードをremoveChildで除去した後、hasChildNodesはfalseを返すため、そこでwhileが終了します。

この状態では、pの中に何もない、つまり<p></p>のような状態になるのでよくないですが、このまっさらな状態から新たなノードを追加していくということがよくあります。これについては、次回解説します。

firstChild

実は、ノードはfirstChildというプロパティを持っています。意味は「最初の子」、つまり「0番目の子ノード」です。だから、当然代入されているのはノードです。書き換えはできません。これを使えば、childNodesを通さずに直接0番目の子を取得できます。つまり、上のサンプルはこう書き換えられます。

while( p.hasChildNodes() ){
    p.removeChild( children.item(0) );
}

↓↓↓

while( p.hasChildNodes() ){
    p.removeChild(p.firstChild);
}
          

ちなみに、こうなると変数childrenは1度も使われなくなるため、代入自体をしなくてもよくなります。また、p.hasChildNodes()は、p.childNodes.length==0としても同じ事ができます。考えてみればわかりますね。

また、派生して、「lastChild」というプロパティもあります。これは「一番最後の子」です。