2022年8月21日

属性とプロパティ

ブラウザはページを読み込むと、HTMLを「読み取り」(別の言葉で「解析」)し、そこからDOMオブジェクトを生成します。要素ノードの場合、ほとんどの標準HTML属性は自動的にDOMオブジェクトのプロパティになります。

たとえば、タグが<body id="page">の場合、DOMオブジェクトはbody.id="page"を持ちます。

しかし、属性とプロパティのマッピングは1対1ではありません!この章では、この2つの概念を区別し、それらをどのように扱うか、いつ同じでいつ異なるかを見ていきます。

DOMプロパティ

組み込みのDOMプロパティはすでに見てきました。たくさんあります。しかし、技術的には誰も私たちを制限しておらず、十分でない場合は、独自に追加できます。

DOMノードは通常のJavaScriptオブジェクトです。変更することができます。

たとえば、document.bodyに新しいプロパティを作成してみましょう。

document.body.myData = {
  name: 'Caesar',
  title: 'Imperator'
};

alert(document.body.myData.title); // Imperator

メソッドも追加できます

document.body.sayTagName = function() {
  alert(this.tagName);
};

document.body.sayTagName(); // BODY (the value of "this" in the method is document.body)

また、Element.prototypeのような組み込みのプロトタイプを変更し、すべての要素に新しいメソッドを追加することもできます

Element.prototype.sayHi = function() {
  alert(`Hello, I'm ${this.tagName}`);
};

document.documentElement.sayHi(); // Hello, I'm HTML
document.body.sayHi(); // Hello, I'm BODY

つまり、DOMプロパティとメソッドは通常のJavaScriptオブジェクトのものと同様に動作します

  • 任意の値を持つことができます。
  • 大文字と小文字が区別されます(elem.NoDeTyPeではなく、elem.nodeTypeと記述します)。

HTML属性

HTMLでは、タグに属性を設定できます。ブラウザがHTMLを解析してタグのDOMオブジェクトを作成すると、*標準*属性を認識し、そこからDOMプロパティを作成します。

そのため、要素に`id`または別の*標準*属性がある場合、対応するプロパティが作成されます。ただし、属性が非標準の場合は、これは発生しません。

例えば

<body id="test" something="non-standard">
  <script>
    alert(document.body.id); // test
    // non-standard attribute does not yield a property
    alert(document.body.something); // undefined
  </script>
</body>

ある要素の標準属性が別の要素では不明である場合があることに注意してください。たとえば、`"type"`は<input>HTMLInputElement)の標準ですが、<body>HTMLBodyElement)の標準ではありません。標準属性は、対応する要素クラスの仕様に記述されています。

ここで確認できます

<body id="body" type="...">
  <input id="input" type="text">
  <script>
    alert(input.type); // text
    alert(body.type); // undefined: DOM property not created, because it's non-standard
  </script>
</body>

そのため、属性が非標準の場合、対応するDOMプロパティはありません。そのような属性にアクセスする方法はありますか?

もちろんです。すべての属性は、次のメソッドを使用してアクセスできます。

  • elem.hasAttribute(name) – 存在を確認します。
  • elem.getAttribute(name) – 値を取得します。
  • elem.setAttribute(name, value) – 値を設定します。
  • elem.removeAttribute(name) – 属性を削除します。

これらのメソッドは、HTMLに記述されている内容とまったく同じように動作します。

また、`elem.attributes`を使用してすべての属性を読み取ることができます。これは、組み込みのAttrクラスに属するオブジェクトのコレクションであり、`name`と`value`プロパティを持ちます。

非標準プロパティを読み取るデモを次に示します。

<body something="non-standard">
  <script>
    alert(document.body.getAttribute('something')); // non-standard
  </script>
</body>

HTML属性には、次の特徴があります。

  • 名前は大文字と小文字が区別されません(`id`は`ID`と同じです)。
  • 値は常に文字列です。

属性を操作する拡張デモを次に示します。

<body>
  <div id="elem" about="Elephant"></div>

  <script>
    alert( elem.getAttribute('About') ); // (1) 'Elephant', reading

    elem.setAttribute('Test', 123); // (2), writing

    alert( elem.outerHTML ); // (3), see if the attribute is in HTML (yes)

    for (let attr of elem.attributes) { // (4) list all
      alert( `${attr.name} = ${attr.value}` );
    }
  </script>
</body>

ご注意ください

  1. getAttribute('About') – 最初の文字は大文字ですが、HTMLではすべて小文字です。しかし、それは問題ではありません。属性名は大文字と小文字が区別されません。
  2. 属性には何でも代入できますが、文字列になります。そのため、ここでは値として`"123"`があります。
  3. 設定した属性を含むすべての属性は、`outerHTML`に表示されます。
  4. `attributes`コレクションは反復可能であり、要素のすべての属性(標準および非標準)を`name`および`value`プロパティを持つオブジェクトとして持ちます。

プロパティと属性の同期

標準属性が変更されると、対応するプロパティが自動的に更新され、(いくつかの例外を除いて)逆も同様です。

以下の例では、`id`が属性として変更され、プロパティも変更されていることがわかります。そして、同じことが逆方向にも起こります

<input>

<script>
  let input = document.querySelector('input');

  // attribute => property
  input.setAttribute('id', 'id');
  alert(input.id); // id (updated)

  // property => attribute
  input.id = 'newId';
  alert(input.getAttribute('id')); // newId (updated)
</script>

しかし、例外があり、たとえば`input.value`は属性→プロパティからのみ同期し、逆方向には同期しません

<input>

<script>
  let input = document.querySelector('input');

  // attribute => property
  input.setAttribute('value', 'text');
  alert(input.value); // text

  // NOT property => attribute
  input.value = 'newValue';
  alert(input.getAttribute('value')); // text (not updated!)
</script>

上記の例では

  • 属性`value`を変更すると、プロパティが更新されます。
  • しかし、プロパティの変更は属性には影響しません。

この「機能」は実際に役立つ場合があります。ユーザーの操作によって`value`が変更された後、HTMLから「元の」値を復元したい場合、属性にその値があるからです。

DOMプロパティは型指定されます

DOMプロパティは常に文字列であるとは限りません。たとえば、`input.checked`プロパティ(チェックボックスの場合)はブール値です

<input id="input" type="checkbox" checked> checkbox

<script>
  alert(input.getAttribute('checked')); // the attribute value is: empty string
  alert(input.checked); // the property value is: true
</script>

他にも例があります。`style`属性は文字列ですが、`style`プロパティはオブジェクトです

<div id="div" style="color:red;font-size:120%">Hello</div>

<script>
  // string
  alert(div.getAttribute('style')); // color:red;font-size:120%

  // object
  alert(div.style); // [object CSSStyleDeclaration]
  alert(div.style.color); // red
</script>

ただし、ほとんどのプロパティは文字列です。

まれに、DOMプロパティの型が文字列であっても、属性と異なる場合があります。たとえば、`href` DOMプロパティは、属性に相対URLまたは単なる`#hash`が含まれている場合でも、常に*完全な* URLです。

例を次に示します。

<a id="a" href="#hello">link</a>
<script>
  // attribute
  alert(a.getAttribute('href')); // #hello

  // property
  alert(a.href ); // full URL in the form http://site.com/page#hello
</script>

HTMLに記述されているとおりに`href`またはその他の属性の値が必要な場合は、`getAttribute`を使用できます。

非標準属性、dataset

HTMLを作成するとき、多くの標準属性を使用します。しかし、非標準のカスタム属性はどうでしょうか?まず、それらが役に立つかどうかを見てみましょう。何のために?

非標準属性は、HTMLからJavaScriptにカスタムデータを渡したり、JavaScript用にHTML要素を「マーク」したりするために使用されることがあります。

このように

<!-- mark the div to show "name" here -->
<div show-info="name"></div>
<!-- and age here -->
<div show-info="age"></div>

<script>
  // the code finds an element with the mark and shows what's requested
  let user = {
    name: "Pete",
    age: 25
  };

  for(let div of document.querySelectorAll('[show-info]')) {
    // insert the corresponding info into the field
    let field = div.getAttribute('show-info');
    div.innerHTML = user[field]; // first Pete into "name", then 25 into "age"
  }
</script>

また、要素のスタイルを設定するためにも使用できます。

たとえば、ここでは注文状態に`order-state`属性が使用されています

<style>
  /* styles rely on the custom attribute "order-state" */
  .order[order-state="new"] {
    color: green;
  }

  .order[order-state="pending"] {
    color: blue;
  }

  .order[order-state="canceled"] {
    color: red;
  }
</style>

<div class="order" order-state="new">
  A new order.
</div>

<div class="order" order-state="pending">
  A pending order.
</div>

<div class="order" order-state="canceled">
  A canceled order.
</div>

`order-state-new`、`order-state-pending`、`order-state-canceled`のようなクラスを持つよりも、属性を使用する方が望ましいのはなぜでしょうか?

属性の方が管理しやすいからです。状態は次のように簡単に変更できます

// a bit simpler than removing old/adding a new class
div.setAttribute('order-state', 'canceled');

しかし、カスタム属性には問題が発生する可能性があります。独自の目的で非標準属性を使用し、後で標準がそれを導入して何かをするようにしたらどうなるでしょうか? HTML言語は生きており、成長しており、開発者のニーズに合わせてより多くの属性が登場しています。そのような場合、予期しない影響が生じる可能性があります。

競合を避けるために、data-*属性が存在します。

「data-」で始まるすべての属性は、プログラマーが使用するために予約されています。これらは`dataset`プロパティで使用できます。

たとえば、`elem`に`"data-about"`という名前の属性がある場合、`elem.dataset.about`として使用できます。

このように

<body data-about="Elephants">
<script>
  alert(document.body.dataset.about); // Elephants
</script>

`data-order-state`のような複数の単語で構成される属性は、キャメルケースになります:`dataset.orderState`。

書き直された「注文状態」の例を次に示します。

<style>
  .order[data-order-state="new"] {
    color: green;
  }

  .order[data-order-state="pending"] {
    color: blue;
  }

  .order[data-order-state="canceled"] {
    color: red;
  }
</style>

<div id="order" class="order" data-order-state="new">
  A new order.
</div>

<script>
  // read
  alert(order.dataset.orderState); // new

  // modify
  order.dataset.orderState = "pending"; // (*)
</script>

`data-*`属性を使用することは、カスタムデータを渡すための有効で安全な方法です。

データ属性は読み取るだけでなく、変更することもできることに注意してください。その後、CSSはそれに応じてビューを更新します。上記の例では、最後の行`(*)`は色を青に変更します。

まとめ

  • 属性 – HTMLに記述されている内容です。
  • プロパティ – DOMオブジェクトにある内容です。

簡単な比較

プロパティ 属性
タイプ 任意の値、標準プロパティは仕様に記述されている型を持ちます 文字列
名前 名前は大文字と小文字が区別されます 名前は大文字と小文字が区別されません

属性を操作するためのメソッドは次のとおりです。

  • elem.hasAttribute(name) – 存在を確認します。
  • elem.getAttribute(name) – 値を取得します。
  • elem.setAttribute(name, value) – 値を設定します。
  • elem.removeAttribute(name) – 属性を削除します。
  • `elem.attributes`はすべての属性のコレクションです。

ほとんどの場合、DOMプロパティを使用することをお勧めします。属性は、DOMプロパティが適していない場合、たとえば、属性が正確に必要な場合にのみ参照する必要があります。

  • 非標準属性が必要です。ただし、`data-`で始まる場合は、`dataset`を使用する必要があります。
  • HTMLに「記述されているとおりに」値を読み取りたいと考えています。DOMプロパティの値は異なる場合があります。たとえば、`href`プロパティは常に完全なURLであり、「元の」値を取得したい場合があります。

タスク

重要度: 5

ドキュメントから`data-widget-name`属性を持つ要素を選択し、その値を読み取るコードを記述してください。

<!DOCTYPE html>
<html>
<body>

  <div data-widget-name="menu">Choose the genre</div>

  <script>
    /* your code */
  </script>
</body>
</html>
<!DOCTYPE html>
<html>
<body>

  <div data-widget-name="menu">Choose the genre</div>

  <script>
    // getting it
    let elem = document.querySelector('[data-widget-name]');

    // reading the value
    alert(elem.dataset.widgetName);
    // or
    alert(elem.getAttribute('data-widget-name'));
  </script>
</body>
</html>
重要度: 3

`style`プロパティを変更することにより、すべての外部リンクをオレンジ色にします。

リンクは次の場合に外部リンクです

  • `href`に`://`が含まれている
  • ただし、`http://internal.com`で始まらない。

<a name="list">the list</a>
<ul>
  <li><a href="http://google.com">http://google.com</a></li>
  <li><a href="/tutorial">/tutorial.html</a></li>
  <li><a href="local/path">local/path</a></li>
  <li><a href="ftp://ftp.com/my.zip">ftp://ftp.com/my.zip</a></li>
  <li><a href="https://node.dokyumento.jp">https://node.dokyumento.jp</a></li>
  <li><a href="http://internal.com/test">http://internal.com/test</a></li>
</ul>

<script>
  // setting style for a single link
  let link = document.querySelector('a');
  link.style.color = 'orange';
</script>

結果は次のようになります

タスクのサンドボックスを開きます。

まず、すべての外部参照を見つける必要があります。

2つの方法があります。

1つ目は、`document.querySelectorAll('a')`を使用してすべてのリンクを見つけ、必要なものをフィルタリングすることです

let links = document.querySelectorAll('a');

for (let link of links) {
  let href = link.getAttribute('href');
  if (!href) continue; // no attribute

  if (!href.includes('://')) continue; // no protocol

  if (href.startsWith('http://internal.com')) continue; // internal

  link.style.color = 'orange';
}

注意:HTMLからの値が必要なので、`link.href`ではなく`link.getAttribute('href')`を使用します。

…もう1つのより簡単な方法は、CSSセレクターにチェックを追加することです

// look for all links that have :// in href
// but href doesn't start with http://internal.com
let selector = 'a[href*="://"]:not([href^="http://internal.com"])';
let links = document.querySelectorAll(selector);

links.forEach(link => link.style.color = 'orange');

サンドボックスで解答を開きます。

チュートリアルマップ