uhyohyo.net

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

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

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

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

innerHTML

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

これは、要素の中身がHTMLで代入されているというものでした。

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

<!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より下の木構造を全部壊して、新しい木構造を一から作っているということになります。木構造を慎重に扱う姿勢のDOMとしては、こんな方法は原始的です。

テキストノード

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

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

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

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

「#text」というノードが、文字列を表します。このノードはテキストノードといいます。DOMでは、文字列は必ずテキストノードとして表されます。テキストノードの登場により、前はpとstrongしかなかった木構造が少し賑やかになりました。

それでは、これを踏まえて、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とは親のことです。つまり、このプロパティは「親ノード」と意味です。parentNodeには、そのノードの親ノードが代入されています。

ただし、このparentNodeは書き換えられません。このparentNodeに別のノードを代入したりしても、親が替わったりしません。見るだけです。このように、ノードのプロパティには、書き換えるとそれが反映されるものと、書き換えられないものがあります。注意しましょう。基本的に、木構造に関連するプロパティは書き換えられません。後々紹介する専用のメソッドを使って木構造をいじる必要があります。例えば、pの親を別の親にするには、まずもとの親から切り離して、それを別の親の下にくっつけるという丁寧な手続きが必要になるのです。

一方、書き換えると反映されるものの代表例が先に紹介したinnerHTMLでした。

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

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

console.log(p.parentNode.tagName);

「BODY」と出ますね。一般にHTML要素は小文字で書かれますが、tagNameが大文字なのは歴史的経緯によるものです。昔は要素名を大文字で書くのが普通でした。

子の取得

さて、今回の目的は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>

こうすると、p要素の子ノードたちを一覧にした配列のようなものが表示されます。実は、ややこしいですが、これは正確には配列ではなくNodeListというものです。

ここで登場したchildNodesプロパティには、子ノードの一覧がNodeListの形で入っています。parentNodeはノードが直接入っていた一方でchildNodesは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番目の子ノードを表示することになります。

テキストノードが表示されます。これも正しいですね。

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

「"STRONG"」と出るので、ちゃんとstrong要素であることが分かります。

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

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

なお、itemメソッドを呼び出す代わりに、配列のような方法でNodeListの中身を得る方法もあります。つまり、次のようにすることもできます。

children[1].tagName

さて、これで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 "t" ←これ
  • strong
    • #text "es"
  • #text "t"

ですね。

さて、実はテキストノードはnodeValueというプロパティを持ちます。このプロパティは、テキストノードの場合、その中身、つまりそのテキストノードが持つ文字列を表します。これは、読むほかに書き換えることもできます

まず、13行目ではnodeValueを読んでいます。「t」が表示されました。その後、nodeValueに新しい値を代入しています。すると、木構造は次のように変化します。

p
  • #text "書き換えました"
  • strong
    • #text "es"
  • #text "t"

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

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

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

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