uhyohyo.net

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

十七章第二回 DOM4的なメソッド

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

実は、DOM4では基本的な機能に対して新しいメソッドがいろいろ追加されました。今回はこれらのメソッドを紹介します。ちなみに、これらのメソッドはjQueryという有名なライブラリの影響を受けているようです。

ノードの分類

メソッドを紹介する前に、DOM4で導入されたノードの分類について解説します。ノードというのは、たくさん種類がありました。要素を表すElement、木構造の頂点にあるDocument、テキストノードであるTextを最もよく使うでしょう。他にDocumentFragmentも使い所がありましたね。他にも、Attr、Comment、DocumentTypeなど普段あまり目にしないタイプのノードもあります。

これらのノードを特徴により分類したものがあります。まず最初に紹介するのはParentNodeです。これは名前の通り、他のノードの親になりえるノードが該当する分類です。具体的には、Document、DocumentFragment、Elementの3種類が該当します。その他のノードは子を持つことはありえません。例えばText、すなわちテキストノードは木構造中の文字列部分を表すノードなので、その中身がさらに子ノードとなることはありません。よって、ParentNodeではないのです。

逆にChildNodeという分類もあります。これは、他のノードの子になりえるノードの分類です。具体的には、DocumentType, Element, Text, Comment, ProcessingInstructionです。

何やら見慣れない種類のノードがいくつかありますね。DocumentType, Comment, ProcessingInstructionは今まで紹介したことがありません。まあ、あまり重要でないから紹介していなかったのですが。一応ここでさらっと紹介しておきます。

DocumentTypeはDOCTYPE宣言、すなわち<!doctype html>の部分を表すノードです。こんなノードもあったのですね。とはいえ、HTML5の時代においてはこのノードはほとんど形骸化してしまったといってもいいでしょう。一応document.doctypeで取得できます。

Commentはその名の通り、コメント、すなわち<!-- こういうの -->を表すノードです。HTML中に書いたコメントは無視されますが、実は木構造中にノードとして存在しているのです。

最後のProcessingInstruction(処理命令)はXML文書でのみ登場するもので、こういうやつを表すノードです。


    <?xml-stylesheet href="foo.xsl" type="text/xsl" ?>
  

このように<?で始まり?>で終わるのがProcessing Instruction(処理命令)で、それを示すノードがProcessingInstructionです。

処理命令では中身の書式は自由です。上の例はhrefとtypeという属性を持つタグのように見えますが、これはxml-stylesheetという処理命令がたまたまこういう文法を持っていただけです。ただし、<?の直後の文字列(今回の場合はxml-stylesheet)の部分はtargetと呼ばれ、他の部分(data)と区別されます。

まあCommentは扱うことがあるかもしれませんが、DocumentTypeとProcessingInstructionはまず使わないでしょう。

ちなみに、Text、Comment、ProcessingInstructionの3種類のノードをまとめた分類であるCharacterDataという分類が存在します。CharacterDataに該当するノードは、中身の文字列をdataプロパティで取得できる点で共通しています。テキストノードは言わずもがな、コメントや処理命令も結局中身はただのテキストだというわけです。CharacterDataは中身がテキストであるノードを表す分類となっています。なお、CharacterDataの概念はDOM4より前から存在していました。

メソッド・プロパティたち

さて、話を戻してメソッドを紹介することにします。上でParentNodeとChildNodeの定義を紹介したのは、これに対して定義されたメソッドが存在するからです。例えばParentNodeが持つメソッドというのは、Document、DocumentFragment、Elementに共通するメソッドということになります。

ちなみに、従来は複数の種類のノードに存在すべきメソッドは全部Nodeに定義されていました。これは、全ての種類のノード(DocumentとかElementとか)がNodeを継承していることを利用しています。例えば、はるか昔に紹介したappendChildは実はNodeが持つメソッドです。だから、Elementに対してもDocumentFragmentに対しても共通に利用できました。しかし、Nodeが持つメソッドということは、子を持たないようなノード、例えばTextに対してもappendChildを呼び出すことができます。ところがテキストノードは子を持たないノードなので、当然子を追加することはできません。実際にやろうとするとエラーになります。そうなると、テキストノードに対してappendChildを呼び出すのは全く無意味ということです。そこでDOM4では、メソッドが無意味になる事態を無くすために、子に関わるメソッドの範囲をParentNodeに制限することにしたのです。

とはいえ、互換性の問題から、appendChildといった旧来のメソッドをいきなり消すことはできません。そこで、ParentNodeにしかない新しいメソッドを導入することにしたのです。appendChildは依然として存在し続けますが、それでも新しいメソッドを作ることに意味はあります。というのも、最近はJavaScriptの開発環境が充実し、自動補完や(TypeScriptでは)型チェックなどもできるようになりました。その精度を向上させることができるでしょう。

前置きが長くなりましたが、それでは最初はParentNodeが持つメソッドを紹介します。ParentNodeの特徴は子を持てることだったので、子に関するメソッドやプロパティがあります。

まずはappendです。これは子ノードを追加するメソッドです。さきほどまでさんざん例に出してきたappendChildが進化したメソッドです。

進化したポイントは、まず引数は1つだけではなく任意の数可能です。これにより、複数のノードを一度に追加することができます。一つ一つappendChildする手間が省けるのでとてもうれしいですね。

もう1つは、テキストノードを挿入したいときはdocument.createTextNodeを使ってテキストノードを作るかわりに、引数に文字列を渡すと勝手にテキストノードに変換してくれる点です。

前回のサンプルに登場した、p要素の中身を作る次の部分をappendを使って書き換えてみましょう。


p.textContent="ああ、";
p.appendChild(time);
p.appendChild(document.createTextNode("もいい天気だなあ。"));

これを書き換えると次のようになります。


p.append("ああ、",time,"もいい天気だなあ。");

記述が短くなり、しかも何をやっているか分かりやすくなりました。とても嬉しいですね。

子ノードを追加するメソッドはもう1つあります。prependです。appendでは親ノードの子の最後に全部追加しますが、prependは最初に追加します。例えば、

A
  • B
  • C
  • D

というときにA.prepend(E,F);を実行した場合次のようになります。

A
  • E
  • F
  • B
  • C
  • D

では、ここでいくつかParentNodeが持つプロパティを紹介します。

まずはchildrenです。これはchildNodes(Nodeに存在)のParentNode版で、HTMLCollectionを返します。childNodesと同じように使えます。

……

ここで、鋭い方ならあることに気づいたかもしれません。DOMというのは第六章でも紹介した通り、HTMLばかりでなくXML文書も扱うことができる仕様です。それなのにHTMLCollectionなどというものが登場している。これは歴史的経緯によるものです。まあ、あまり気にしないようにするのがよいでしょう。

他に、firstElementChildlastElementChildというプロパティがあります。これは、子ノードでElementであるもののうち最初/最後のものを返します。従来(Nodeのプロパティとして)存在したfirstChildとlastChildは、すべての子ノードのうち最初/最後のノードを返すことになります。例えば、


<ul>
  <li>AAA</li>
  <li>BBB</li>
  <li>CCC</li>
</ul>

のulに対してそのfirstChildはulタグとliタグの間にある改行のテキストノードとなりますが、firstElementChildは最初のli要素になります。正直テキストノードとかどうでもいいという場面も多いので、こちらが役に立つ場面が多いでしょう。

他には、childElementCountというプロパティは、子ノードのうちElementであるものの数を返します。

前回紹介したquerySelectorAllも実はParentNodeのメソッドとして定義されています。他に、AllではないquerySelectorというメソッドもあり、こちらはセレクタにマッチする要素のうち最初のひとつのみを返します。すなわち、返り値はElementです(1つもなければnullになります)。

他に、queryAllqueryという似た名前のメソッドがあります。こちらはセレクタではなく相対セレクタ(> pみたいな)にマッチする要素を返します。


次に、ChildNodeが持つメソッドを紹介します。こちらの特徴は親がいるということなので、やはり親子関係に関わるメソッドが多いです。

まず、最初のメソッドはremoveです。これは引数なしのメソッドで、親ノードから自身を取り除きます。親がいない(まだ他のノードの子として追加されていない)場合は何も起きません。

このメソッドの画期的なところは、親の存在を意識しなくていいところです。今までノードを木構造から除去するには、親ノードのremoveChildメソッドを呼ぶ必要がありました。こちらは子ノードのメソッドとなっている点が新しいですね。

次に、replaceWithです。これは親のreplaceChildを呼ぶのに相当するメソッドで、引数に渡されたノードで自身を置き換えます。先ほど紹介したappendやprependと同様に、複数のノードや文字列を引数に渡すことができます。

他には、beforeafterメソッドがあります。これは、自分の前または後に、兄弟として引数のノードを追加するメソッドです。もちろん、引数はreplaceWith等と同様に複数指定可能です。これは特に便利ですね。今まではparentNodeのinsertBeforeなどを駆使する必要がありました。

さらに、プロパティとして、previousElementSiblingnextElementSiblingがあります。previousElementSiblingは、自身の兄弟ノードで、自身より前にあってElementであるもののうち最初のもの(一番自身に近いもの)を返します。nextElementSiblingは自身より後ろにあるElementで最初のものを返します。


というわけで、今回はDOM4時代の基本的なメソッドをいくつか紹介しました。とても便利なメソッドたちですが、現在(2015年5月)まだブラウザの実装がぜんぜん追い付いていないことは書き終わってから気付きました。使う際は気をつけましょう。