innerHTML += ... な書き方について
はてなブックマークで『後でツッコミする』と書いたとおり、ちょっとツッコミたいと思う。
ツッコミ記事のつもりが、自分がツッコミされることとなり、ダメダメな記事です。それでも良ければお読み下さいw
// サンプル1: パフォーマンスが悪い var ul = document.querySelector('#output'); for ( var i = 0; i < data.length; i++ ) { ul.innerHTML += ‘<li>’ + data[i] + ‘</li>’; }
上記コードはダメなコードであり、理由は、
li要素をループが回るたびに追加しているため
レイアウト・レンダリングによる遅さ
というのが主な原因として挙げられています。
ボクは、このコードがダメな理由は、もちろんその理由もあるんだけど、もっとダメな理由があると思っています。
そうでないと、下記コードのような、同じようにループ中に何度もDOMツリーへの更新をするコードとのパフォーマンスの差を説明できないからです。
var ul = document.querySelector('#output'); var li; for (var i = 0; i < data.length; i++) { li = document.createElement("li"); li.textContent = data[i]; ul.appendChild(li); }
innerHTML += ... がやっていること
単純に ... を追加しているわけではないです。
innerHTML の Getter アクセスがある
element.innerHTML += "a"
は element.innerHTML = element.innerHTML + "a"
と等価です。
右辺で element.innerHTML の値を得て、 + "a" するわけです。ここで、element のDOMツリーを文字列へ変換する処理があります。
innerHTML への Setter はツリーの全置換えである
element.innerHTML への代入処理は
- elementの子要素をクリアする
- 与えられた文字列を、DOMオブジェクトに変換する
- 変換したDOMオブジェクトを子要素として追加する
ということをしていると思われます。 += 演算子を使っていると、まるで追加処理のように見えてしまいますが、全取っ替えです。
ツッコミ先のページでは、3つ目の子要素に追加だけが原因かのように言っていますが、本当に3つ目が原因なのでしょうか? 少々疑問です。
検証
ちょいコード書き直した
document.body.insertAdjacentHTML("beforeend", '<div id="test"></div>'); var $test = document.getElementById("test"); var count = 100; function clearDiv() { $test.innerHTML = ""; } function test_1 () { console.time("innerHTML"); for (var i = 0; i < count; ++i) { $test.innerHTML += "<span>" + i + "</span>"; } console.timeEnd("innerHTML"); clearDiv(); } function test_2 () { console.time("innerHTML2"); for (var i = 0, html = ""; i < count; ++i) { html += "<span>" + i + "</span>"; $test.innerHTML = html; } console.timeEnd("innerHTML2"); clearDiv(); } function test_3 () { console.time("insertAdjacentHTML"); for (var i = 0; i < count; ++i) { $test.insertAdjacentHTML("BeforeEnd", "<span>" + i + "</span>"); } console.timeEnd("insertAdjacentHTML"); clearDiv(); } function test_4 () { console.time("appendChild"); for (var i = 0, span; i < count; ++i) { span = document.createElement("span"); span.textContent = i; $test.appendChild(span); } console.timeEnd("appendChild"); clearDiv(); }
テキトウな検証なので、細かいところは分かりませんが、まぁ参考値ということで Firefox で実施した結果を載せておきます。
innerHTML | innerHTML 2 | insertAdjacentHTML | appendChild | |
---|---|---|---|---|
Time | 22.41ms | 18.36ms | 1.48ms | 1.16ms |
主な処理内容 (ループ中) |
|
|
|
|
本当に正しい検証かどうか怪しいんだけど、innerHTML の getter が結構な遅さを誇っているのではないかなと思うわけです。getter アクセスでコストが掛かっているわけではなさそう。
以上、レンダリングに関わる部分をループ中に処理するのがダメな理由として innerHTML を使用したサンプルは悪手なんじゃない? というツッコミでした。
検証コードのミスを修正したら、自分が思ったのとは違う結果になってしまった。えーと、偉そうな事書いて、すみませんでした><