DOMノードについて、より深く見ていきましょう。
この章では、DOMノードとは何かをより詳しく見て、最もよく使われるプロパティについて学びます。
DOMノードクラス
DOMノードの種類によって、プロパティが異なる場合があります。例えば、<a>
タグに対応する要素ノードにはリンク関連のプロパティがあり、<input>
タグに対応する要素ノードには入力関連のプロパティがあります。テキストノードは要素ノードと同じではありません。しかし、すべてのDOMノードクラスは単一の階層構造を形成しているため、共通のプロパティとメソッドも存在します。
各DOMノードは、対応する組み込みクラスに属します。
階層のルートはEventTargetで、Nodeによって継承され、他のDOMノードはそれを継承します。
図を示します。以下で説明します。
クラスは以下のとおりです。
-
EventTarget – すべてのルートとなる「抽象」クラスです。
このクラスのオブジェクトは作成されません。これはベースとして機能し、すべてのDOMノードが sogenannte 「イベント」をサポートできるようにします。イベントについては後で学習します。
-
Node – DOMノードのベースとなる「抽象」クラスです。
これは、
parentNode
、nextSibling
、childNodes
などのコアツリー機能を提供します(これらはゲッターです)。Node
クラスのオブジェクトは作成されません。しかし、それから継承する(そしてNode
機能を継承する)他のクラスがあります。 -
Documentは、歴史的な理由から、しばしば
HTMLDocument
によって継承されます(最新の仕様では規定されていません) - ドキュメント全体です。グローバルオブジェクトの
document
は、まさにこのクラスに属します。これはDOMへのエントリポイントとして機能します。 -
CharacterData – 次のクラスによって継承される「抽象」クラスです。
-
Element – DOM要素の基本クラスです。
nextElementSibling
、children
などの要素レベルのナビゲーションや、getElementsByTagName
、querySelector
などの検索メソッドを提供します。ブラウザはHTMLだけでなく、XMLとSVGもサポートしています。そのため、
Element
クラスは、より具体的なクラスであるSVGElement
、XMLElement
(ここでは必要ありません)、およびHTMLElement
のベースとして機能します。 -
最後に、HTMLElementは、すべてのHTML要素の基本クラスです。ほとんどの場合、これを使用します。
具体的なHTML要素によって継承されます。
- HTMLInputElement –
<input>
要素のクラスです。 - HTMLBodyElement –
<body>
要素のクラスです。 - HTMLAnchorElement –
<a>
要素のクラスです。 - …などです。
- HTMLInputElement –
特定のプロパティとメソッドを持つ独自のクラスを持つタグは他にもたくさんありますが、<span>
、<section>
、<article>
などの一部の要素には特定のプロパティがないため、HTMLElement
クラスのインスタンスです。
そのため、特定のノードのプロパティとメソッドの完全なセットは、継承の連鎖の結果として得られます。
例として、<input>
要素のDOMオブジェクトを考えてみましょう。これはHTMLInputElementクラスに属します。
プロパティとメソッドは、(継承順にリストされている)重ね合わせとして取得します。
HTMLInputElement
– このクラスは入力固有のプロパティを提供します。HTMLElement
– 共通のHTML要素メソッド(およびゲッター/セッター)を提供します。Element
– 汎用要素メソッドを提供します。Node
– 共通のDOMノードプロパティを提供します。EventTarget
– イベントのサポートを提供します(後述)。- …そして最後に、
Object
から継承されるため、`hasOwnProperty`などの「プレーンオブジェクト」メソッドも利用できます。
DOMノードのクラス名を確認するには、オブジェクトには通常`constructor`プロパティがあることを思い出してください。これはクラスコンストラクターを参照し、`constructor.name`はその名前です。
alert( document.body.constructor.name ); // HTMLBodyElement
…または、単に`toString`することもできます。
alert( document.body ); // [object HTMLBodyElement]
継承を確認するために`instanceof`を使用することもできます。
alert( document.body instanceof HTMLBodyElement ); // true
alert( document.body instanceof HTMLElement ); // true
alert( document.body instanceof Element ); // true
alert( document.body instanceof Node ); // true
alert( document.body instanceof EventTarget ); // true
ご覧のとおり、DOMノードは通常のJavaScriptオブジェクトです。継承にはプロトタイプベースのクラスを使用します。
ブラウザで`console.dir(elem)`を使用して要素を出力することで、これも簡単に確認できます。コンソールには、`HTMLElement.prototype`、`Element.prototype`などが表示されます。
ほとんどのブラウザは、開発ツールで`console.log`と`console.dir`の2つのコマンドをサポートしています。これらのコマンドは、引数をコンソールに出力します。JavaScriptオブジェクトの場合、これらのコマンドは通常同じことを行います。
しかし、DOM要素の場合は異なります。
- `console.log(elem)`は、要素のDOMツリーを表示します。
- `console.dir(elem)`は、要素をDOMオブジェクトとして表示します。これは、プロパティを調べるのに適しています。
`document.body`で試してみてください。
仕様では、DOMクラスはJavaScriptを使用して記述されていませんが、通常は理解しやすい特別なインターフェース記述言語(IDL)が使用されています。
IDLでは、すべてのプロパティの前に型が付けられます。例えば、`DOMString`、`boolean`などです。
以下に、コメント付きの抜粋を示します。
// Define HTMLInputElement
// The colon ":" means that HTMLInputElement inherits from HTMLElement
interface HTMLInputElement: HTMLElement {
// here go properties and methods of <input> elements
// "DOMString" means that the value of a property is a string
attribute DOMString accept;
attribute DOMString alt;
attribute DOMString autocomplete;
attribute DOMString value;
// boolean value property (true/false)
attribute boolean autofocus;
...
// now the method: "void" means that the method returns no value
void select();
...
}
「nodeType」プロパティ
`nodeType`プロパティは、DOMノードの「タイプ」を取得するための、もう1つの「昔ながらの」方法を提供します。
数値です。
- 要素ノードの場合は`elem.nodeType == 1`です。
- テキストノードの場合は`elem.nodeType == 3`です。
- ドキュメントオブジェクトの場合は`elem.nodeType == 9`です。
- 仕様には、他にもいくつかの値があります。
例えば、
<body>
<script>
let elem = document.body;
// let's examine: what type of node is in elem?
alert(elem.nodeType); // 1 => element
// and its first child is...
alert(elem.firstChild.nodeType); // 3 => text
// for the document object, the type is 9
alert( document.nodeType ); // 9
</script>
</body>
最新のスクリプトでは、`instanceof`や他のクラスベースのテストを使用してノードタイプを確認できますが、`nodeType`の方が簡単な場合があります。`nodeType`は読み取り専用で、変更することはできません。
タグ: nodeNameとtagName
DOMノードが与えられた場合、`nodeName`または`tagName`プロパティからタグ名を読み取ることができます。
例えば、
alert( document.body.nodeName ); // BODY
alert( document.body.tagName ); // BODY
`tagName`と`nodeName`に違いはありますか?
確かに、違いは名前には反映されていますが、実際には微妙です。
- `tagName`プロパティは、`Element`ノードにのみ存在します。
- `nodeName`は、任意の`Node`に対して定義されています。
- 要素の場合、`tagName`と同じ意味です。
- 他のノードタイプ(テキスト、コメントなど)の場合、ノードタイプを表す文字列です。
言い換えれば、`tagName`は要素ノードによってのみサポートされます(`Element`クラスに由来するため)が、`nodeName`は他のノードタイプについても何かを伝えることができます。
例として、`document`とコメントノードの`tagName`と`nodeName`を比較してみましょう。
<body><!-- comment -->
<script>
// for comment
alert( document.body.firstChild.tagName ); // undefined (not an element)
alert( document.body.firstChild.nodeName ); // #comment
// for document
alert( document.tagName ); // undefined (not an element)
alert( document.nodeName ); // #document
</script>
</body>
要素のみを扱う場合は、`tagName`と`nodeName`の両方を使用できます。違いはありません。
ブラウザには、ドキュメントを処理するための2つのモードがあります。HTMLモードとXMLモードです。通常、WebページにはHTMLモードが使用されます。XMLモードは、ブラウザが`Content-Type: application/xml+xhtml`というヘッダーを持つXMLドキュメントを受信したときに有効になります。
HTMLモードでは、`tagName/nodeName`は常に大文字になります。`<body>`または`<BoDy>`のいずれの場合でも`BODY`です。
XMLモードでは、大文字と小文字は「そのまま」保持されます。現在では、XMLモードはめったに使用されません。
innerHTML: 内容
innerHTMLプロパティを使用すると、要素内のHTMLを文字列として取得できます。
変更することもできます。そのため、ページを変更するための最も強力な方法の1つです。
この例では、document.body
の内容を表示し、それを完全に置き換えます。
<body>
<p>A paragraph</p>
<div>A div</div>
<script>
alert( document.body.innerHTML ); // read the current contents
document.body.innerHTML = 'The new BODY!'; // replace it
</script>
</body>
無効な HTML を挿入しようとすると、ブラウザはエラーを修正します。
<body>
<script>
document.body.innerHTML = '<b>test'; // forgot to close the tag
alert( document.body.innerHTML ); // <b>test</b> (fixed)
</script>
</body>
innerHTML
が <script>
タグをドキュメントに挿入する場合、それは HTML の一部になりますが、実行されません。
注意: “innerHTML+=” は完全な上書きを行います。
elem.innerHTML+="more html"
を使用して、要素に HTML を追加できます。
このように
chatDiv.innerHTML += "<div>Hello<img src='smile.gif'/> !</div>";
chatDiv.innerHTML += "How goes?";
しかし、これは _追加_ ではなく、完全な上書きであるため、非常に注意する必要があります。
技術的には、これら2行は同じことを行います。
elem.innerHTML += "...";
// is a shorter way to write:
elem.innerHTML = elem.innerHTML + "..."
言い換えれば、innerHTML+=
は次のように動作します。
- 古い内容は削除されます。
- 代わりに新しい
innerHTML
(古いものと新しいものの連結)が書き込まれます。
コンテンツが「ゼロクリア」され、最初から書き直されるため、すべての画像やその他のリソースが再読み込みされます。.
上記の chatDiv
の例では、chatDiv.innerHTML+="How goes?"
という行は HTML コンテンツを再作成し、smile.gif
を再読み込みします(キャッシュされていることを願います)。 chatDiv
に他のテキストや画像がたくさんある場合、再読み込みがはっきりと見えます。
他にも副作用があります。たとえば、既存のテキストがマウスで選択されている場合、ほとんどのブラウザは innerHTML
の書き換え時に選択を解除します。また、訪問者が入力したテキストを含む <input>
がある場合、そのテキストは削除されます。などです。
幸いなことに、innerHTML
の他に HTML を追加する方法がいくつかあり、すぐにそれらを学習します。
outerHTML: 要素の完全な HTML
outerHTML
プロパティには、要素の完全な HTML が含まれています。これは innerHTML
に要素自体を加えたものです。
例を次に示します。
<div id="elem">Hello <b>World</b></div>
<script>
alert(elem.outerHTML); // <div id="elem">Hello <b>World</b></div>
</script>
注意: innerHTML
とは異なり、outerHTML
への書き込みは要素を変更しません。代わりに、DOM 内の要素を置き換えます。
奇妙に聞こえますが、実際奇妙なので、ここで個別に注意しておきます。見てください。
次の例を考えてみましょう。
<div>Hello, world!</div>
<script>
let div = document.querySelector('div');
// replace div.outerHTML with <p>...</p>
div.outerHTML = '<p>A new element</p>'; // (*)
// Wow! 'div' is still the same!
alert(div.outerHTML); // <div>Hello, world!</div> (**)
</script>
本当に奇妙に見えますよね?
(*)
の行では、div
を <p>A new element</p>
に置き換えました。外部ドキュメント (DOM) では、<div>
の代わりに新しいコンテンツが表示されます。しかし、(**)
の行でわかるように、古い div
変数の値は変更されていません!
outerHTML
の割り当ては、DOM 要素 (この場合は変数 'div' によって参照されるオブジェクト) を変更するのではなく、DOM から削除して、新しい HTML をその場所に挿入します。
つまり、div.outerHTML=...
で起こったことは次のとおりです。
div
はドキュメントから削除されました。- 別の HTML の
<p>A new element</p>
がその場所に挿入されました。 div
はまだ古い値を保持しています。新しい HTML はどの変数にも保存されませんでした。
ここでエラーが発生しやすいです。div.outerHTML
を変更してから、まるで新しいコンテンツが含まれているかのように div
を操作し続けることです。しかし、そうではありません。そのようなことは innerHTML
には当てはまりますが、outerHTML
には当てはまりません。
elem.outerHTML
に書き込むことはできますが、書き込み対象の要素 ('elem') は変更されないことに注意する必要があります。代わりに、新しい HTML がその場所に配置されます。 DOM をクエリすることで、新しい要素への参照を取得できます。
nodeValue/data: テキストノードのコンテンツ
innerHTML
プロパティは、要素ノードに対してのみ有効です。
テキストノードなどの他のノードタイプには、対応するものとして nodeValue
と data
プロパティがあります。この 2 つは実用上ほぼ同じで、仕様上のわずかな違いしかありません。そのため、短い方の data
を使用します。
テキストノードとコメントのコンテンツを読み取る例
<body>
Hello
<!-- Comment -->
<script>
let text = document.body.firstChild;
alert(text.data); // Hello
let comment = text.nextSibling;
alert(comment.data); // Comment
</script>
</body>
テキストノードの場合は、それらを読み取ったり変更したりする理由が想像できますが、コメントの場合はなぜでしょうか?
開発者は、次のように HTML に情報を埋め込んだり、テンプレートの指示を埋め込んだりすることがあります。
<!-- if isAdmin -->
<div>Welcome, Admin!</div>
<!-- /if -->
…すると、JavaScript は data
プロパティからそれを読み取り、埋め込まれた命令を処理できます。
textContent: 純粋なテキスト
textContent
は、要素内の _テキスト_ にアクセスします。テキストのみで、すべての <tags>
は除外されます。
例えば、
<div id="news">
<h1>Headline!</h1>
<p>Martians attack people!</p>
</div>
<script>
// Headline! Martians attack people!
alert(news.textContent);
</script>
ご覧のとおり、すべての <tags>
が切り取られたかのようにテキストのみが返されますが、その中のテキストは残ります。
実際には、そのようなテキストを読み取ることはめったにありません。
textContent
への書き込みは、テキストを「安全な方法」で書き込むことができるため、はるかに役立ちます。
たとえば、ユーザーが入力した任意の文字列を表示したいとします。
innerHTML
を使用すると、すべての HTML タグを含む「HTML として」挿入されます。textContent
を使用すると、「テキストとして」挿入され、すべての記号は文字どおりに扱われます。
2 つを比較してみましょう。
<div id="elem1"></div>
<div id="elem2"></div>
<script>
let name = prompt("What's your name?", "<b>Winnie-the-Pooh!</b>");
elem1.innerHTML = name;
elem2.textContent = name;
</script>
- 最初の
<div>
は名前を「HTML として」取得します。すべてのタグはタグになるので、太字の名前が表示されます。 - 2 番目の
<div>
は名前を「テキストとして」取得するため、文字どおり<b>Winnie-the-Pooh!</b>
が表示されます。
ほとんどの場合、ユーザーからのテキストを期待し、それをテキストとして扱いたいと考えています。サイトに予期しない HTML が含まれることは望ましくありません。 textContent
への割り当てはまさにそれを実現します。
「hidden」プロパティ
「hidden」属性と DOM プロパティは、要素が表示されるかどうかを指定します。
HTML で使用したり、JavaScript を使用して次のように割り当てることができます。
<div>Both divs below are hidden</div>
<div hidden>With the attribute "hidden"</div>
<div id="elem">JavaScript assigned the property "hidden"</div>
<script>
elem.hidden = true;
</script>
技術的には、hidden
は style="display:none"
と同じように機能します。ただし、書くのが短くなります。
点滅する要素の例を次に示します。
<div id="elem">A blinking element</div>
<script>
setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>
その他のプロパティ
DOM 要素には、特にクラスに依存する追加のプロパティもあります。
value
-<input>
、<select>
、<textarea>
(HTMLInputElement
、HTMLSelectElement
…) の値。href
-<a href="...">
(HTMLAnchorElement
) の「href」。id
- すべての要素 (HTMLElement
) の「id」属性の値。- …その他多数…
例えば、
<input type="text" id="elem" value="value">
<script>
alert(elem.type); // "text"
alert(elem.id); // "elem"
alert(elem.value); // value
</script>
ほとんどの標準 HTML 属性には対応する DOM プロパティがあり、そのようにアクセスできます。
特定のクラスでサポートされているプロパティの完全なリストを知りたい場合は、仕様書で確認できます。たとえば、HTMLInputElement
は https://html.spec.whatwg.org/#htmlinputelement に dokumentiertされています。
または、それらをすばやく取得したい場合や、具体的なブラウザの仕様に興味がある場合は、console.dir(elem)
を使用して要素を出力し、プロパティを読み取ることができます。あるいは、ブラウザの開発者ツールの [要素] タブで「DOM プロパティ」を確認することもできます。
まとめ
各 DOM ノードは特定のクラスに属します。クラスは階層を形成します。プロパティとメソッドの完全なセットは、継承の結果として得られます。
主な DOM ノードのプロパティは次のとおりです。
nodeType
- ノードがテキストノードか要素ノードかを確認するために使用できます。数値で表され、要素の場合は
1
、テキストノードの場合は3
、その他のノードタイプの場合は他のいくつかの値になります。読み取り専用です。 nodeName/tagName
- 要素の場合はタグ名(XML モードでない限り大文字)。要素以外のノードの場合、
nodeName
はそれが何であるかを表します。読み取り専用です。 innerHTML
- 要素の HTML コンテンツ。変更可能です。
outerHTML
- 要素の完全な HTML。
elem.outerHTML
への書き込み操作は、elem
自体には影響しません。代わりに、外部コンテキストで新しい HTML に置き換えられます。 nodeValue/data
- 要素以外のノード (テキスト、コメント) のコンテンツ。この 2 つはほぼ同じで、通常は
data
を使用します。変更可能です。 textContent
- 要素内のテキスト: HTML からすべての
<tags>
を除いたもの。これに書き込むと、すべての特殊文字とタグがテキストとして扱われ、要素内にテキストが配置されます。ユーザー生成テキストを安全に挿入し、不要な HTML の挿入を防ぐことができます。 hidden
true
に設定すると、CSS のdisplay:none
と同じ効果があります。
DOM ノードには、クラスに応じて他のプロパティもあります。たとえば、<input>
要素 (HTMLInputElement
) は value
、type
をサポートし、<a>
要素 (HTMLAnchorElement
) は href
などをサポートします。ほとんどの標準 HTML 属性には、対応する DOM プロパティがあります。
ただし、次の章で説明するように、HTML 属性と DOM プロパティは常に同じであるとは限りません。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepenなど)を使用してください。