自分でこう書きながら、実は首を傾げていたのだけどやっとわかった。
404 Blog Not Found:WEB+DB PRESS vol.35pp.57まず速度ですが、innerHTMLは代入時にHTMLの構文解析が入るので、速度的にはDOM操作が有利です。期待に反してそうでないのは、404 Blog Not Found:javascript - DOM vs innerHTML benchmark on MacBook Proでの指摘した通り。このあたりはamachangにちゃんと査読してもらった方がよかったのではないか?
InnerHTMLは速くない。速く見えるだけだ。
その証拠として、以下を見て欲しい。
<script type="text/javascript"> //<![CDATA[ function benchmark(button, countid){ function $(id){ return document.getElementById(id) } function now(){ return (new Date).getTime() } var canvas = $('canvas'); var count = $('count').value; var started = 0; var counter = 0; var tid = 0; var delay = 0; var innerHTML = canvas.innerHTML = $('html').value; var dom = [];document.createDocumentFragment(); for (var i = 0; i < canvas.childNodes.length; i++){ dom[i] = canvas.childNodes[i].cloneNode(true); } var run = { innerHTML : function(){ canvas.innerHTML = counter % 2 ? '' : innerHTML; }, DOM : function(){ if ( counter % 2 ){ while(canvas.firstChild) canvas.removeChild(canvas.firstChild); }else{ for (var i = 0; i < dom.length; i++) canvas.appendChild(dom[i]); } } }[button.value]; var repeater = function(){ run(); if (++counter == count){ window.clearTimeout(tid); var elapsed = (now() - started) $('log').innerHTML += button.value + '\t' + elapsed + '\tms' + '\t' + elapsed/count + '\tms/op<br>\n'; button.disabled = false; }else{ tid = window.setTimeout(repeater, delay); } }; started = now(); button.disabled = true; tid = window.setTimeout(repeater, delay); } //]]> </script> <div style="border: dotted 1px; padding: 0.5em"> <textarea id="html" cols="64" rows="8"> Dan Kogai's Home Page is at <a href="http://www.dan.co.jp/">http://www.dan.co.jp/</a>. </textarea> <div id="canvas"></div> <input id="count" type="text" value="100">回 <input type="submit" value="innerHTML" onclick="benchmark(this, 'count')"> <input type="submit" value="DOM" onclick="benchmark(this, 'count')"> <div id="log" style="font-family: monospace"></div> </div>
このように、実際に結果をいちいち表示させるようにした場合、DOM操作とinnerHTMLでほとんど差が出ない。 少なくとも10倍以上差が出ることはありえない。
それでは、innerHTMLは高速に見えるのか?
ここからは憶測になるが、まず外していないと思う。
ヒントは、
IT戦記 - はじめての雑誌><「イベント駆動な DOM」と「エフェクト」と「パフォーマンス」について書きました!
の
WEB+DB Press vol. 35 p.64イベントには「DOMイベント」「タイマイベント」「XMLHttpRequestイベント」の3つの種類があり
記事には書かれていないが、実はもう一つ「ブラウザ内部イベント」というものがタイマーイベントとして存在すると考えるとつじつまが合う。このイベントはブラウザは定期的、おそらく画面の書き換え速度である10-30msごとに実行される。
これはこんな仕組みになっていると考えられる。pseudoscriptで書くとこんな感じだろうか
if (document.changedNodes){ for (var i = 0; i < document.changedNodes.length; i++){ var node = document.changedNodes[i]; if (node.innerHTML){ // 実際の node.childNodes は immutableなのでこうは書けない node.childNodes = node.innerHTML.str2nodes(); } } document.redraw(); }
だから、実際にinnerHTMLを書き換えても、それだけではブラウザーは上の隠しプロパティchangedNodesに文字列を登録するだけで何もしない。実際にそれがDOM Treeなって再描画されるのは「ブラウザ内部イベント」発生時だけなのだ。
ゆえに、innerHTML自体は文字列を操作するだけであり、DOM Treeそのものをいじることに比べたら「その場」で払うコストはずっと安い。しかし、ブラウザ内部イベントの時に、未払い分をしっかり取り立てるわけだ。
だから、パフォーマンス向上の本当の秘訣は、innerHTMLを使うか否かではなく、いかにイベント数を減らせるかということになる。
少し深く考えればわかることだったが、10倍以上の差に目がくらんでいたようだ。反省。
しかしそれでもなおinnerHTMLと比べてdom操作まわりは使いづらい。いちいち長いしcamelizeされたmethod namesは読みづらいし書きづらいし、なぜ appendChild() だの removeChild() だので一つづつやらなければならないかわからない。多分循環参照を防ぐための配慮だと思うのだけど、めんどいったらありゃしない....
Dan the Man with Too Many Browsers to Browse
昔、IEのまたいとこぐらいのブラウザいじってた経験から類推すると、たぶんIEも同じ。
だから何?
と言われると返す言葉もありませんが・・・