2022年8月5日

ノードのプロパティ: type、tag、contents

DOMノードについて、より深く見ていきましょう。

この章では、DOMノードとは何かをより詳しく見て、最もよく使われるプロパティについて学びます。

DOMノードクラス

DOMノードの種類によって、プロパティが異なる場合があります。例えば、<a>タグに対応する要素ノードにはリンク関連のプロパティがあり、<input>タグに対応する要素ノードには入力関連のプロパティがあります。テキストノードは要素ノードと同じではありません。しかし、すべてのDOMノードクラスは単一の階層構造を形成しているため、共通のプロパティとメソッドも存在します。

各DOMノードは、対応する組み込みクラスに属します。

階層のルートはEventTargetで、Nodeによって継承され、他のDOMノードはそれを継承します。

図を示します。以下で説明します。

クラスは以下のとおりです。

  • EventTarget – すべてのルートとなる「抽象」クラスです。

    このクラスのオブジェクトは作成されません。これはベースとして機能し、すべてのDOMノードが sogenannte 「イベント」をサポートできるようにします。イベントについては後で学習します。

  • Node – DOMノードのベースとなる「抽象」クラスです。

    これは、parentNodenextSiblingchildNodesなどのコアツリー機能を提供します(これらはゲッターです)。Nodeクラスのオブジェクトは作成されません。しかし、それから継承する(そしてNode機能を継承する)他のクラスがあります。

  • Documentは、歴史的な理由から、しばしばHTMLDocumentによって継承されます(最新の仕様では規定されていません) - ドキュメント全体です。

    グローバルオブジェクトのdocumentは、まさにこのクラスに属します。これはDOMへのエントリポイントとして機能します。

  • CharacterData – 次のクラスによって継承される「抽象」クラスです。

    • Text – 要素内のテキストに対応するクラスです。例えば、<p>Hello</p>Helloです。
    • Comment – コメントのクラスです。コメントは表示されませんが、各コメントはDOMのメンバーになります。
  • Element – DOM要素の基本クラスです。

    nextElementSiblingchildrenなどの要素レベルのナビゲーションや、getElementsByTagNamequerySelectorなどの検索メソッドを提供します。

    ブラウザはHTMLだけでなく、XMLとSVGもサポートしています。そのため、Elementクラスは、より具体的なクラスであるSVGElementXMLElement(ここでは必要ありません)、およびHTMLElementのベースとして機能します。

  • 最後に、HTMLElementは、すべてのHTML要素の基本クラスです。ほとんどの場合、これを使用します。

    具体的なHTML要素によって継承されます。

特定のプロパティとメソッドを持つ独自のクラスを持つタグは他にもたくさんありますが、<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.dir(elem)`と`console.log(elem)`

ほとんどのブラウザは、開発ツールで`console.log`と`console.dir`の2つのコマンドをサポートしています。これらのコマンドは、引数をコンソールに出力します。JavaScriptオブジェクトの場合、これらのコマンドは通常同じことを行います。

しかし、DOM要素の場合は異なります。

  • `console.log(elem)`は、要素のDOMツリーを表示します。
  • `console.dir(elem)`は、要素をDOMオブジェクトとして表示します。これは、プロパティを調べるのに適しています。

`document.body`で試してみてください。

仕様のIDL

仕様では、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`の両方を使用できます。違いはありません。

タグ名は、XMLモードの場合を除いて、常に大文字です。

ブラウザには、ドキュメントを処理するための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+= は次のように動作します。

  1. 古い内容は削除されます。
  2. 代わりに新しい 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 プロパティは、要素ノードに対してのみ有効です。

テキストノードなどの他のノードタイプには、対応するものとして nodeValuedata プロパティがあります。この 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>
  1. 最初の <div> は名前を「HTML として」取得します。すべてのタグはタグになるので、太字の名前が表示されます。
  2. 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>

技術的には、hiddenstyle="display:none" と同じように機能します。ただし、書くのが短くなります。

点滅する要素の例を次に示します。

<div id="elem">A blinking element</div>

<script>
  setInterval(() => elem.hidden = !elem.hidden, 1000);
</script>

その他のプロパティ

DOM 要素には、特にクラスに依存する追加のプロパティもあります。

  • value - <input><select><textarea> (HTMLInputElementHTMLSelectElement…) の値。
  • 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 プロパティがあり、そのようにアクセスできます。

特定のクラスでサポートされているプロパティの完全なリストを知りたい場合は、仕様書で確認できます。たとえば、HTMLInputElementhttps://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) は valuetype をサポートし、<a> 要素 (HTMLAnchorElement) は href などをサポートします。ほとんどの標準 HTML 属性には、対応する DOM プロパティがあります。

ただし、次の章で説明するように、HTML 属性と DOM プロパティは常に同じであるとは限りません。

タスク

重要度: 5

ネストされた ul/li として構造化されたツリーがあります。

<li> に対して以下を表示するコードを書いてください。

  1. 内部にあるテキスト (サブツリーを含まない)
  2. ネストされた <li> の数 - 深くネストされたものを含むすべての子孫。

新しいウィンドウでデモ

タスクのサンドボックスを開く。

<li> をループ処理してみましょう。

for (let li of document.querySelectorAll('li')) {
  ...
}

ループ内で、すべての li 内のテキストを取得する必要があります。

li の最初の子ノード、つまりテキストノードからテキストを読み取ることができます。

for (let li of document.querySelectorAll('li')) {
  let title = li.firstChild.data;

  // title is the text in <li> before any other nodes
}

次に、子孫の数を li.getElementsByTagName('li').length として取得できます。

サンドボックスで解答を開く。

重要度: 5

スクリプトは何を表示しますか?

<html>

<body>
  <script>
    alert(document.body.lastChild.nodeType);
  </script>
</body>

</html>

ここに落とし穴があります。

<script> の実行時には、ブラウザはまだページの残りの部分を処理していないため、最後の DOM ノードは正確には <script> です。

したがって、結果は 1 (要素ノード) です。

<html>

<body>
  <script>
    alert(document.body.lastChild.nodeType);
  </script>
</body>

</html>
重要度: 3

このコードは何を表示しますか?

<script>
  let body = document.body;

  body.innerHTML = "<!--" + body.tagName + "-->";

  alert( body.firstChild.data ); // what's here?
</script>

答え: **`BODY`**。

<script>
  let body = document.body;

  body.innerHTML = "<!--" + body.tagName + "-->";

  alert( body.firstChild.data ); // BODY
</script>

何が起こっているのか、ステップバイステップで見ていきましょう。

  1. <body> の内容はコメントに置き換えられます。 body.tagName == "BODY" なので、コメントは <!--BODY--> になります。覚えているように、HTML では tagName は常に大文字です。
  2. コメントが唯一の子ノードになったので、body.firstChild で取得します。
  3. コメントの data プロパティは、その内容 (<!--...--> 内) です: "BODY"
重要度: 4

document はどのクラスに属しますか?

DOM 階層におけるその位置はどこですか?

Node または Element、あるいは HTMLElement から継承していますか?

次のように出力することで、どのクラスに属しているかを確認できます。

alert(document); // [object HTMLDocument]

または

alert(document.constructor.name); // HTMLDocument

したがって、documentHTMLDocument クラスのインスタンスです。

階層におけるその位置はどこですか?

仕様書を参照することもできますが、手動で確認する方が速いです。

__proto__ を介してプロトタイプチェーンをたどってみましょう。

ご存知のとおり、クラスのメソッドはコンストラクタの `prototype` にあります。たとえば、`HTMLDocument.prototype` にはドキュメント用のメソッドがあります。

また、prototype 内にはコンストラクタ関数への参照があります。

alert(HTMLDocument.prototype.constructor === HTMLDocument); // true

クラスの名前を文字列として取得するには、constructor.name を使用できます。クラス Node まで、ドキュメントのプロトタイプチェーン全体に対してこれを実行してみましょう。

alert(HTMLDocument.prototype.constructor.name); // HTMLDocument
alert(HTMLDocument.prototype.__proto__.constructor.name); // Document
alert(HTMLDocument.prototype.__proto__.__proto__.constructor.name); // Node

これが階層です。

また、console.dir(document) を使用してオブジェクトを調べて、__proto__ を開いてこれらの名前を確認することもできます。コンソールは内部で constructor からそれらを取得します。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善のための提案がある場合は、コメントする代わりに GitHub の issue またはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 短いコードを挿入するには<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkrjsbincodepenなど)を使用してください。