innerHTML や​ jQuery.html() は​ HTMLを​そのまま​取得できるわけではない

element.innerHTML を使うと、その要素内の HTML を取得することができます。Mozilla Developer Network (MDN) には次のように書いてあります。

innerHTML は、与えられた要素に含まれる全てのマークアップ と内容を設定または取得します。

-- element.innerHTML – MDC Doc Center より

また、jQuery.html() も、innerHTML と同様、HTML を取得することができます。

Get the HTML contents of the first element in the set of matched elements.

-- .html() – jQuery API より

でも、これらのメソッドを使っても、HTML のソースをそのまま取得できるわけではないことをご存知でしたか?私は知らずにハマってしまいました。ということで、以下、調べてみた結果です。

例 1

<div id="main">
  <ul>
    <li>coffee</li>
    <li>coke</li>
    <li>red bull</li>
  </ul>
</div>

<script>
  var html = document.getElementById('main').innerHTML;
  console.log(html);
</script>

この例では、次のような期待通りの HTML が返ってきます。

<ul>
  <li>coffee</li>
  <li>coke</li>
  <li>red bull</li>
</ul>

例 2

しかし、次の場合はどうでしょうか?

<div id="main">
  <p><div>foo</div></p>
</div>

<script>
  var html = document.getElementById('main').innerHTML;
  console.log(html);
</script>

コンソールに出力されるのは

<p><div>foo</div></p>

だと期待するかもしれませんが、実際は

<p></p>
<div>foo</div>
<p></p>

という文字列が返ってきます。jQuery.html() の場合も同じ結果になります。

なぜこのようなことが起こるのでしょうか?不思議に思い、Quora で質問を投げてみました。すると、すぐにどなたかが答えてくれました。(Quora すげー!)また、Twitter でも同様の質問を投げてみたところ、@hogenishi1122 さんが答えてくれました。(感謝!)

innerHTML や jQuery.html() は HTML のソースをそのまま返すわけではなく、ブラウザが一度 DOM ツリーに変換したものを返すとのこと。従って、invalid な HTML コードだと、ブラウザが解釈した DOM ツリーが返ってくるというわけでした。

invalid な HTML を書かなければ、さほど問題にはならないかもしれませんが、必ずしも世の中がすべて valid だとは限りません。記憶の片隅に留めておけば、無用な苦労をすることもないかと思います。