innerHTML より insertAdjacentHTML を使う

#はじめに

とある要素の中をいじりたいという時 `innerHTML` で操作することもあるかもしれません。
しかし、実はこれはあまりよろしくない。

#`innerHTML` は既存の要素を破壊（？）する

`innerHTML` を書き換えるということはつまり、要素の中身をすべて新しいものに書き換えるということです。
既存の要素は、新たな要素として扱われてしまいます。

##サンプル

例えば、以下のようなHTMLとJavaScriptコードがあったとします。

```html:HTML
<div id="contentBlock">
  <button id="clickButton">Click Here</button>
</div>
```

```js:JavaScript
var clickButton = document.getElementById('clickButton');
clickButton.addEventListener('click', function () {
  alert('Alert!');
});
```

ボタンをクリックするとアラートが出るだけの簡単なやつです。
では、button要素の前に `innerHTML` を使用して文字列を追加してみます。

```js:JavaScript
var contentBlock = document.getElementById('contentBlock');
contentBlock.innerHTML = '<b>Test:</b>' + contentBlock.innerHTML;
```

するとどうでしょう、ボタンをクリックしても反応しなくなりました。

\#contentBlock 内のbutton要素は `innerHTML` によって**HTML文字列として**取得され、`innerHTML` によって**新たなHTMLとして** \#contentBlock に挿入されます。
なので、挿入前と挿入後のbutton要素は別物なのです。

#`insertAdjacentHTML` は既存の要素を破壊しない

[element.insertAdjacentHTML - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML)

`insertAdjacentHTML` は文字列をHTMLとしてパースしたものを指定した箇所に挿入するメソッドです。

```js:構文
element.insertAdjacentHTML(position, text);
```

`position` で挿入する箇所を指定、`text` で挿入するHTML文字列となっています。
`position` には以下の4つが指定できます。

- `'beforebegin'`
- `'afterbegin'`
- `'beforeend'`
- `'afterend'`

それぞれの位置関係は以下です。

```html
<!-- beforebegin -->
<element>
  <!-- afterbegin -->
  <child>Text</child>
  <!-- beforeend -->
</element>
<!-- afterend -->
```

##サンプル

先ほどと同じように以下のようなHTMLとJavaScriptコードがあったとします。

```html:HTML
<div id="contentBlock">
  <button id="clickButton">Click Here</button>
</div>
```

```js:JavaScript
var clickButton = document.getElementById('clickButton');
clickButton.addEventListener('click', function () {
  alert('Alert!');
});
```

では、button要素の前に `insertAdjacentHTML` を使用して文字列を追加してみます。

```js:JavaScript
var contentBlock = document.getElementById('contentBlock');
contentBlock.insertAdjacentHTML('afterbegin', '<b>Test:</b>');
```

見た目的には `innerHTML` の時と変わりませんが、button要素のイベントが残ったままです。

#`insertAdjacentHTML` は `innerHTML` よりも処理が速い

[MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML) にも

> 余分なシリアル化のステップを回避できる分、 `innerHTML` への代入による直接的な操作よりもはるかに高速な動作となります。

とあります。

簡単に計測してみます。

```js:JavaScript
function timeTest() {
  var
    div01 = document.createElement('div'),
    div02 = document.createElement('div'),
    i;

  console.time('innerHTML');
  for (i = 0; i < 1000; i++) {
    div01.innerHTML = div01.innerHTML + 'test';
  }
  console.timeEnd('innerHTML');

  console.time('insertAdjacentHTML');
  for (i = 0; i < 1000; i++) {
    div02.insertAdjacentHTML('beforeend', 'test');
  }
  console.timeEnd('insertAdjacentHTML');
}
```

結果は Chrome 61 では以下でした。（10回実行したうちのスコアがいいもの）

```text
innerHTML: 35.122802734375ms
insertAdjacentHTML: 3.9697265625ms
```

10倍近い差があるみたいです。

おそらく `innerHTML` ではDOMツリーの再構築の負担が大きいものと思われます。

#おまけ：要素を空っぽにしたいのなら `textContent`

とある要素の中身を空っぽにしたいのなら `innerHTML` よりも `textContent` の方がパフォーマンスはいいです。

```js:JavaScript
document.getElementById('foo').textContent = '';
```

#おまけ：HTML要素そのものを挿入したいなら `insertAdjacentElement`

文字列ではなく要素を挿入したいのなら `insertAdjacentElement` もありますよというお話。

[Element.insertAdjacentElement() - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentElement)

第2引数が `HTMLElement` になるだけで基本は `insertAdjacentHTML` と変わりません。

```js:JavaScript
var
  contentBlock = document.getElementById('contentBlock'),
  insetElement = document.createElement('b');

insetElement.textContent = 'Test:';

contentBlock.insertAdjacentElement('afterbegin', insetElement);
```