uhyohyo.net

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

二章第三回 基本的な操作とテキストノード

今回から、いよいよWebページにも多少動きが出てきます。

innerHTML

前回、ちらっと「innerHTML」というプロパティが出てきました。実は、HTMLElementなら何でも持っているプロパティです。

そのタグの中身が(文字列で)そのまま代入されているというものでした。

それでは、今度はそこに新しい中身を代入したらどうなるでしょう。なんと、実際に書きかわります

<!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');
      p.innerHTML = "書き換えました";
    </script>
  </body>
</html>
        

このサンプルを実行してみると、pの中身は「t<strong>es</strong>t」だったはずなのに、「書き換えました」と変わってしまっています。そう、JavaScript側がinnerHTMLに新しい中身を代入したからです。

動きのあるWebページへの第一歩という感じですね。ちなみに、HTMLタグももちろん使えます。いろいろ入れる中身を変えてみると面白いと思います。

しかし、実は、この方法もまだまだ原始的な方法です。なぜなら、上のサンプルで、p要素は次のような構造を持っていました。

 p
|
└――strong
        

線が1本しかないので何か格好悪いですが、これでも立派な木構造です。

ここで、innerHTMLは、pより下の木構造を全部壊して、新しい木構造を1から作っているということになります。木構造を慎重に扱う姿勢のDOMとしては、こんな方法は原始的です。

テキストノード

それでは、どんな方法なら原始的でないのでしょうか。それは、まとめて壊さずちゃんとひとつずつ外してひとつずつ付けたり、あるいは壊さずに書き換えたりするという方法です。

今回の場合は、p要素の中身を書き換えたいのでした。上の木構造を見ても分かる通り、p要素の中にあるのはstrong要素だけのようです。

しかし、実際のHTMLを見てみると、p要素の中にはstrong要素の他にも「文字列」があります。この木構造では、ただの文字列の部分が表されていません。そこで、ただの文字列も表すと、次のようになります。

 p
|
├――#text
|
├――strong
|   |
|   └――#text
|
└――#text
        

「#text」というのが、文字列です。こういう謎の書き方をするのは、これもDOMで定められているからです。

さて、前はpとstrongしかなかった木構造ですが、少し賑やかになりました。

pの子が「strong」だけだったのに、「#text」「strong」「#text」の3つに増えました。また、strongにも子ができました。

木構造では子は順番に並んでいるので、p要素の中身は「t」「<strong>es</strong>」「t」に区切られ、タグがあればその要素が子に、タグがなければその文字列が子になります。

つまり、一番上の#textの中身は「t」で、STRONGの子の#textの中身は「es」で、その下のは「t」であることが分かります。

このような文字列もノードの一種で、テキストノードといいます。前回の要素ノード(HTMLElement)と並んでよく使うノードの種類です。

それでは、これを踏まえて、p要素の書き換え方を解説していきます。

親と子の取得

親の取得

あるノードのオブジェクトがあるとき、その親や子を取得する方法があります。次のサンプルを見てみましょう。

<!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');
      console.log(p.parentNode);
    </script>
  </body>
</html>
        

「HTMLBodyElement」のように表示されます。

今回、getElementByIdでp要素を取得して、それが持つ「parentNode」というプロパティを表示しています。

英語で、「parent」とは「親」、「Node」はノードです。つまり、「親ノード」ということになります。そのままですね。つまり、このプロパティparentNodeは、そのノードの親ノードが代入されているということになります。

ただし、このparentNodeは書き換えられません。このparentNodeに別のノードを代入したりしても、親が替わったりしません。見るだけです。このように、ノードのプロパティには、書き換えるとそれが反映されるものと、書き換えられないものがあります。注意しましょう。書き換えると反映されるものの代表例が先に紹介したinnerHTMLでした。

pの親を別の親にするには、まずもとの親から切り離して、それを別の親の下にくっつけるという丁寧な手続きが必要になるのです。

さて、getElementByIdで取得したpの親はbody要素なので、p.parentNodeはbody要素のHTMLElementだったというわけです。HTMLBodyElementといいます。

ちなみに、HTMLElementなら、「tagName」というプロパティにタグの名前が入っています。これも書き換え不可です。

console.log(p.parentNode.tagName);

「BODY」と出ますね。

子の取得

さて、今回の目的はp要素の中身を書き換えることなので、p要素より上の部分(親)はあまり関係ありません。関係あるのは子のほうです。

それでは、子の取得の仕方を解説します。

<!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');
      console.log(p.childNodes);
    </script>
  </body>
</html>
        

NodeListと表示されます。

「child」は「子」で「Nodes」はノードなので、childNodesというプロパティはそのまま「子ノード」であることが分かります。

しかし、実は今回childNodesに代入されているのはHTMLElementではありません

なぜなら、前にも解説したように子は複数ある可能性があるからです。今回の場合も、pは3つの子ノードを持っていますね。#text、strong、#textです。

そこで、childNodesには、「子ノードのリスト(一覧)」が入っています。これはNodeList(ノードリスト)というオブジェクトで、ノードとはまた別のものです。

つまり、このNodeListの扱い方がわかれば子ノードを取得できるというわけです。

NodeList

まず、NodeListはlengthというプロパティを持っています。これは、NodeListにあるノードの数です。今回の場合、pの子ノードの数ということになります。これは配列変数と似たような感じですね。

また、itemというメソッドも持っています。このメソッドは数値の引数を1つとり、(引数)番目のノードを返します。

というのも、NodeListでは、ノードは0番目,1番目,2番目....のように番号がついて順番に並んでいます。ノードはlength個あるので、最後のノードは(length-1)番目ということになります。

このあたりは、本当に配列とよく似ています。

とりあえず、実際に見てみましょう。

<!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;
      console.log(children.length);
    </script>
  </body>
</html>
        

このサンプルでは、p.childNodesをchildrenに代入しています。NodeListもオブジェクトの一種なので、childrenとp.childNodesは同じものを指しています。

つまり、children.lengthを表示しているということは、p.childNodes.lengthを表示しているのと同じというわけです。

「3」が表示されます。p.childNodesはp要素の子ノードのリストなので、p要素の子ノードが3個あるということを示しています。これは正しいですね。

次に、

console.log(children.item(0));

としてみましょう。「0番目のノード」、つまりp要素の0番目の子ノードを表示することになります。

「Text」のように表示されます。「Text」とは、テキストノードのことです。つまり、0番目のノードはテキストノードであるというわけです。これも正しいですね。

同様に、1番目はHTMLElement、2番目はTextです。1番目が「HTMLElement」なのでHTML要素ですが、何の要素かを知るには上で紹介したtagNameプロパティを使いましょう。console.log(children.item(1).tagName);

「"STRONG"」と出るので、ちゃんとstrong要素であることが分かります。ちなみに、tagNameプロパティの中身が大文字になっているかもしれませんが、理由は歴史的経緯によります(HTML5の前まではタグ名は大文字で定義されていました)。

ちなみに、関数呼び出しの関数名()の直後にプロパティの.プロパティ名がきていますが、これは問題ありません。

まず関数呼び出しが処理されて、その戻り値のオブジェクト(この場合は1番目のノード)に対してプロパティを参照しています。

さて、これでNodeListから子ノードを得る方法が分かりました。同時に、親ノードを通して子のテキストノードを得ることもできるようになりました。

テキストノードの操作

それでは、このテキストノードについて詳しく解説していきます。

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

<!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);	//テキストノード
      console.log(textnode.nodeValue);
      textnode.nodeValue = "書き換えました";
    </script>
  </body>
</html>
        

変数textnodeには、children.item(0)を代入しています。childrenは上と同じでpの子ノードのNodeListなので、textnodeは、そのNodeListの0番目の要素、つまりpの0番目の(最初の)子ということになります。

要するにテキストノードが代入されています。

 p
|
├――#text     ←これ
|
├――strong
|   |
|   └――#text
|
└――#text
        

ですね。

さて、これのnodeValueというプロパティをまず表示しています。このプロパティは、テキストノードの場合、その中身、つまりそのテキストノードが持つ文字列を表します。

つまり、「t」が表示されました。

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

こういう状態です。

その後、そのnodeValueに代入しています。このnodeValueは書き換え可能なプロパティで、書き換えた内容が反映されます。つまり、ここに代入したことで、

 p
|
├――#text      … "書き換えました"
|
├――strong
|   |
|   └――#text  … "es"
|
└――#text      … "t"
      

こういうようになったということです。

その結果、「書き換えましたest」と表示されています。

これは、まったく木構造を汚さず、ただその木構造の末端のテキストノードを書き換えただけです。これは原始的ではない方法です。

しかし、最終的に書き換わったp要素は「書き換えましたest」です。innerHTMLのときと比べて「est」が余計ですね。これは、「est」は今回操作したノードとは別のノードの部分だから、影響を受けていないことによります。strong要素の子のテキストノードと、p要素の2つめのテキストノードのnodeValueを""(空)にしてしまってもよいのですが、それだと中身のないテキストノードが残ってしまうことになります。やはりそれはよくないです。

そこで、木構造からノードそのものを消し去るという作業が必要になってきます。次回はこれについて解説します。